Repo disclosed

This commit is contained in:
bs
2021-05-11 09:52:37 +02:00
commit 3ffa53d717
64 changed files with 6273 additions and 0 deletions
@@ -0,0 +1,217 @@
package application.parsing;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import application.helpers.Utils;
import application.helpers.wrappers.WrappedException;
import application.res.Text;
/**
* This class wraps a file together with its writer and readable name together,
* so the writer can be written on repeatedly without having to be opened each
* and every time.
*
* @author Bianca
*
*/
public class FileWrapper
{
/**
* The file to be wrapped
*/
private File file;
/**
* The filename in a readable format so it can be displayed to the user.
*/
private String readableFileName;
/**
* A writer for the file
*/
private BufferedWriter bout;
/**
* Creates a file in temp directory for this filename and creates the writer.
*
* @param filename Exact filename ending
* @param readableFilename First part of the readable name
* @param readableSubFilename Second part of the readable name
* @throws WrappedException If there was an error creating the file or the
* writer
*/
private FileWrapper(String filename, String readableFilename, String readableSubFilename) throws WrappedException
{
try
{
file = Files.createTempFile("health_data", "__" + filename).toFile();
readableFileName = createReadableFileName(readableFilename, readableSubFilename);
file.deleteOnExit();
bout = new BufferedWriter(new FileWriter(file, true));
}
catch (IOException e)
{
throw new WrappedException(null, Text.E_CREATE_TEMP_FILES);
}
}
/**
* Creates a file in temp directory for this filename and creates the writer.
*
* @param filename Exact filename ending
* @param readableFilename Readable name
* @throws WrappedException If there was an error creating the file or the
* writer
*/
public FileWrapper(String filename, String readableFilename) throws WrappedException
{
this(filename, readableFilename, null);
}
/**
* Creates a file in temp directory for this filename and creates the writer.
* The header will be written with the given header coloumns.
*
* @param filename Exact filename ending
* @param readableFilename First part of the readable name
* @param readableSubFilename Second part of the readable name
* @param headerColoums The header coloumn names
* @throws WrappedException If there was an error creating the file or the
* writer
*/
public FileWrapper(String filename, String readableFilename, String readableSubFilename, String[] headerColoums)
throws WrappedException
{
this(filename, readableFilename, readableSubFilename);
try
{
writeHeaderColumn(headerColoums, true);
bout.newLine();
}
catch (IOException e)
{
throw new WrappedException(null, Text.E_CREATE_TEMP_FILES);
}
}
/**
* Combines the two parts of the readable filename into on.
*
* @param partOne The first (main) part of the filename.
* @param partTwo The second (additional) part of the filename.
* @return The described value.
*/
public static String createReadableFileName(String partOne, String partTwo)
{
if (partTwo == null)
{
return String.format(Text.F_FILE_DESC, partOne, "");
}
return String.format(Text.F_FILE_DESC, partOne, partTwo);
}
/**
* Creates a readable filename.
*
* @param name The intended filename.
* @return The described value.
*/
public static String createReadableFileName(String name)
{
return createReadableFileName(name, null);
}
/**
* Writes the header column in the files (names of attributes and sub attributes
* (in lists))
*
* @param attributes names of the attributes
* @param top attributes can be nested. If this is {@code True}, then
* this is the top level attribute. If this is {@code False}
* than it's a sub attribute.
* @throws IOException if there was an error writing
*/
private void writeHeaderColumn(String[] attributes, boolean top) throws IOException
{
for (int i = 0; i < attributes.length; i++)
{
// write attributeName
bout.write(attributes[i]);
// in case of sub attributes write sested elements
if (attributes[i].equalsIgnoreCase(Text.TAG_NAME_META_DATA_ENTRY))
{
bout.write("[[");
writeHeaderColumn(Utils.META_DATA_ATTR, false);
bout.write("]*]");
}
if (attributes[i].equalsIgnoreCase(Text.TAG_NAME_WORKOUT_EVENT))
{
bout.write("[[");
writeHeaderColumn(Utils.WORKOUT_EVENT_ATTR, false);
bout.write("]*]");
}
if (attributes[i].equalsIgnoreCase(Text.TAG_NAME_WORKOUT_ROUTE))
{
bout.write("[[");
writeHeaderColumn(Utils.WORKOUT_ROUTE_ATTR, false);
bout.write("]*]");
}
if (attributes[i].equalsIgnoreCase(Text.TAG_NAME_HR_LIST))
{
bout.write("[[");
writeHeaderColumn(Utils.HR_LIST_ATTR, false);
bout.write("]*]");
}
if (attributes[i].equalsIgnoreCase(Text.TAG_NAME_IB_PER_MINUTES))
{
bout.write("[[");
writeHeaderColumn(Utils.IB_PER_MINUTES_ATTR, false);
bout.write("]*]");
}
//Elements are separated with ';' on top level and with ',' on all other levels
if (i < attributes.length - 1)
{
if (top)
{
bout.write(";");
}
else
{
bout.write(",");
}
}
}
}
/**
* Returns the writer for the file.
* @return the described value.
*/
public BufferedWriter getBufferedWriter()
{
return bout;
}
/**
* Returns the file as an object.
* @return The described value.
*/
public File getFile()
{
return file;
}
/**
* Returns the readable filename.
* @return The described value.
*/
public String getReadableFilename()
{
return readableFileName;
}
}
@@ -0,0 +1,150 @@
package application.parsing;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.HashMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import application.helpers.wrappers.Occupation;
import application.helpers.wrappers.WrappedException;
import application.helpers.wrappers.WriteSelection;
import application.res.Text;
/**
* This class coordinates the work with the parser (Reading and Writing).
* @author Bianca
*
*/
public class ParsingWrapper
{
/** The {@link ReadHandler} who will give an overview of the health data given**/
private ReadHandler read;
/** factory needed for the parser**/
private SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
/** the parser**/
private SAXParser saxParser;
/** The health data file**/
private File srcFile;
/**
* Constructor tries to get access to the health data file
*
* @param file file object of the data
* @throws Exception in case there was an error while getting access
*/
public ParsingWrapper(File file) throws Exception
{
srcFile=file;
saxParser = saxParserFactory.newSAXParser();
read = new ReadHandler();
saxParser.parse(srcFile, read);
if(read.getExportDate()==null) //wrong file / error reading
{
throw new Exception();
}
}
/**
* Gives access to the read data
* @return
*/
public ReadHandler getRead()
{
return read;
}
/**
* Writes the chosen data to the temp-directory
* @param selection The users selection on what data to use
* @param job The given job of the user
* @return the created files. Keys are the readable filenames
* @throws WrappedException If there was an error with the writing to files
*/
public HashMap<String,File> writeSelectedDataToFiles(WriteSelection selection, Occupation job) throws WrappedException
{
try
{
WriteHandler write = new WriteHandler(selection, job);
saxParser.parse(srcFile, write);
if(write.getErrorMessages()!=null)
{
throw new WrappedException(null, write.getErrorMessages());
}
return write.getAllFiles();
}
catch (Exception e)
{
e.printStackTrace();
throw new WrappedException(e, Text.E_WRITE_TO_TEMP_FILES);
}
}
/**
* Wraps the files from the temp directory into a zip file and places it in the given directory.
* @param files The files to wrap up
* @param targetDir The directory on where to save them
* @return The created zip file or {@code null} if writing / creating didn't work
*/
public File writeToZipFile(HashMap<String,File> files, File targetDir)
{
File target = new File(targetDir, "Export.zip");
int c=0;
//file with the same name could already exist
while(target.exists())
{
c++;
target = new File(targetDir, "Export("+c+").zip");
}
boolean created=false;
try
{
created=target.createNewFile();
}
catch(Exception e)
{
e.printStackTrace();
//file couldn't be created due to exception
}
if(!created)
{ //file couldn't be created, so writing impossible
return null;
}
try(FileOutputStream fos = new FileOutputStream(target);
ZipOutputStream zipOut = new ZipOutputStream(fos))
{
for (String key : files.keySet())
{
File srcFile=files.get(key);
if(srcFile!=null)
{
try(FileInputStream fis = new FileInputStream(srcFile))
{
String filename= srcFile.getName();
filename=filename.substring(filename.indexOf("__")+2);
ZipEntry zipEntry = new ZipEntry(filename);
zipOut.putNextEntry(zipEntry);
byte[] bytes = new byte[1024];
int length;
while((length = fis.read(bytes)) >= 0)
{
zipOut.write(bytes, 0, length);
}
}
}
}
}
catch(Exception e)
{
e.printStackTrace();
//Problem with the file streams, Nothing a user could change so:
return null;
}
return target;
}
}
@@ -0,0 +1,287 @@
package application.parsing;
import java.text.ParseException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import application.enums.BiologicalSex;
import application.enums.BloodType;
import application.enums.EntryType;
import application.enums.SkinType;
import application.helpers.Utils;
import application.res.Text;
/**
* This class is in charge of reading in the data of the file and getting an overview of the data.
* @author Bianca
*
*/
public class ReadHandler extends DefaultHandler
{
/** The date the health data was exported**/
private String exportDate;
/** the biological sex given in the health data **/
private BiologicalSex bSex;
/** the blood type given in the health data **/
private BloodType bloodType;
/** the skin type given in the health data **/
private SkinType skinType;
/** the region code given in the health data **/
private String regionCode;
/** the date of birth as a string given in the health data**/
private String dateOfBirthString;
/** the date of birth saved in a calendar**/
private Calendar dateOfBirth;
/** contains as a key the name of tags (e.g EntryTypes) and as value the number of occurrences**/
private HashMap<String, Integer> names=new HashMap<>();
/** contains as a key the name of the sub workout and as value the number of occurrences **/
private HashMap<String, Integer> subWorkouts=new HashMap<>();
/** contains as a key the name of the sub records and as value the number of occurrences **/
private HashMap<String, Integer> subRecords=new HashMap<>();
/** keeps track of the tag layer. With every new element we increase the layer by one**/
private int overallLayer=0;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
{
//increase layer
if(overallLayer>0)
{
overallLayer++;
}
if (qName.equalsIgnoreCase(Text.TAG_NAME_HEALTH_DATA))
{ //outermost tag (overallLayer==0)
overallLayer++;
regionCode=attributes.getValue(Text.TAG_ATTR_REGION_CODE);
}
else if (qName.equalsIgnoreCase(EntryType.EXPORT_DATE.getValue()))
{
exportDate=attributes.getValue(Text.TAG_ATTR_EXPORT_DATE);
}
else if (qName.equalsIgnoreCase(Text.TAG_NAME_ME_INFO))
{
bSex = BiologicalSex.getValue(attributes.getValue(Text.TAG_ATTR_BIOLOGICAL_SEX));
dateOfBirthString = attributes.getValue(Text.TAG_ATTR_DATE_OF_BIRTH);
bloodType = BloodType.getValue(attributes.getValue(Text.TAG_ATTR_BLOOD_TYPE));
skinType = SkinType.getValue(attributes.getValue(Text.TAG_ATTR_SKIN_TYPE));
if(dateOfBirthString!=null&&!dateOfBirthString.equals(""))
{
try
{
dateOfBirth=new GregorianCalendar();
dateOfBirth.setTime(Utils.formatDateOfBirth.parse(dateOfBirthString));
} catch (ParseException e)
{
dateOfBirth=null;
e.printStackTrace();
//do nth if parsing error
}
}
}
else if (qName.equalsIgnoreCase(EntryType.RECORD.getValue()))
{
String subRecord=attributes.getValue(Text.TAG_ATTR_RECORD_TYPE);
Integer j = subRecords.get(subRecord);
if (j == null)
{
j = 0;
}
subRecords.put(subRecord, ++j);
}
else if (qName.equalsIgnoreCase(EntryType.WORKOUT.getValue()))
{
String subWorkout=attributes.getValue(Text.TAG_ATTR_WORKOUT_TYPE);
Integer j = subWorkouts.get(subWorkout);
if (j == null)
{
j = 0;
}
subWorkouts.put(subWorkout, ++j);
}
if(overallLayer==2) //layer 2 means direct descendants of the document root (HealthData-tag)
{
Integer v=names.get(qName);
if (v==null)
{
v=0;
}
names.put(qName, ++v);
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException
{
//decrease layer
if(overallLayer>0)
{
overallLayer--;
}
}
/**
* Returns the selected value from ME or ExportDate.
*
* @param type Value to find the info for.
* @return Found value if it was a valid entry of ME or the export Date,
* {@code null} otherwise
*/
public String getMeInfo(EntryType type)
{
if (type.equals(EntryType.BIOLOGICAL_SEX))
{
return bSex.getReadableValue();
} else if (type.equals(EntryType.DATE_OF_BIRTH))
{
return getDateOfBirth();
}
if (type.equals(EntryType.BLOOD_TYPE))
{
return bloodType.getReadableValue();
}
if (type.equals(EntryType.SKIN_TYPE))
{
return skinType.getReadableValue();
}
return null;
}
/**
* Returns the day the health data was exported
* @return the described value.
*/
public String getExportDate()
{
return exportDate;
}
/**
* Returns the region code given in the file.
* @return the described value.
*/
public String getRegionCode()
{
return regionCode;
}
/**
* Finds out how many Entries of the given EntryTypes can be found in the
* document
*
* @param type EntryType to find the count to
* @return 0 if there are none, otherwise the correct number of entries
*/
public int getTypeNumber(EntryType type)
{
if (names.get(type.getValue()) == null)
{
return 0;
}
return names.get(type.getValue());
}
/**
* Returns the year of birth if the data was given in the file. Otherwise returns -1.
* @return the described value.
*/
public int getDateOfBirthYear()
{
if(dateOfBirth!=null)
{
return dateOfBirth.get(Calendar.YEAR);
}
return -1;
}
/**
* Returns the month of birth if the data was given in the file. Otherwise returns -1.
* @return the described value.
*/
public int getDateOfBirthMonth()
{
if(dateOfBirth!=null)
{
return dateOfBirth.get(Calendar.MONTH)+1;
}
return -1;
}
/**
* Returns the day of birth if the data was given in the file. Otherwise returns -1.
* @return the described value.
*/
public int getDateOfBirthDay()
{
if(dateOfBirth!=null)
{
return dateOfBirth.get(Calendar.DAY_OF_MONTH);
}
return -1;
}
/**
* Returns the SubWorkouts with their respective occurrences
* @return The described value
*/
public HashMap<String, Integer> getSubWorkouts()
{
return subWorkouts;
}
/**
* Returns the SubRecords with their respective occurrences
* @return The described value
*/
public HashMap<String, Integer> getSubRecords()
{
return subRecords;
}
/**
* Returns the date of birth as the String read in the file
* @return The described value.
*/
public String getDateOfBirth()
{
return dateOfBirthString;
}
/**
* Returns the date of birth split into 3 parts (day, month, year)
* @return the described value
*/
public HashMap<String, Integer> getSplitDateOfBirth()
{
HashMap<String, Integer> res= new HashMap<>();
if(getDateOfBirthDay()>0)
{
res.put(Text.DAY, getDateOfBirthDay());
}
if(getDateOfBirthMonth()>0)
{
res.put(Text.MONTH, getDateOfBirthMonth());
}
if(getDateOfBirthYear()>0)
{
res.put(Text.YEAR, getDateOfBirthYear());
}
return res;
}
/**
* Returns the biological sex as given in the file
* @return the described value
*/
public BiologicalSex getBSex()
{
return bSex;
}
}
@@ -0,0 +1,632 @@
package application.parsing;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.text.ParseException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import application.enums.BiologicalSex;
import application.enums.BloodType;
import application.enums.EntryType;
import application.enums.SkinType;
import application.helpers.Utils;
import application.helpers.wrappers.AttributeDataInfoTuple;
import application.helpers.wrappers.GeneralSaveInfo;
import application.helpers.wrappers.MeSaveInfo;
import application.helpers.wrappers.Occupation;
import application.helpers.wrappers.WrappedException;
import application.helpers.wrappers.WriteSelection;
import application.res.Text;
public class WriteHandler extends DefaultHandler
{
/**
* Contains the StringBuilders for the nested attributes which need to be added
* to the complete Row. Key is the tag name of the nested attribute
*/
private HashMap<String, AttributeDataInfoTuple> nestedAttributes;
/** The text to be written as one row into the files **/
private StringBuilder completeRow;
/** the selection of the user **/
private WriteSelection selection;
/** all files which will be written. Key is the readable name **/
private HashMap<String, FileWrapper> allFiles;
/**
* In case of records and workouts there will be sub elements. The name will be
* saved here.
**/
private String subElementName = null;
/**
* All error messages will be appended here as {@link DefaultHandler} does not
* permit other exceptions in the startElement and endElement methods
**/
private StringBuilder errorMessages;
/**
* Initializes the variables and creates all files needed for the selected data
*
* @param selection The selection of the user
* @param job The job given by the user
*/
public WriteHandler(WriteSelection selection, Occupation job)
{
this.selection = selection;
this.errorMessages = new StringBuilder();
resetSubAttributes();
// figure out what is selected
// create files for the selected
allFiles = new HashMap<>();
try
{
boolean me = false;
for (EntryType meElem : EntryType.ALL_ME_ONES)
{
if (this.selection.entrySelected(meElem))
{
me = true;
}
}
if (!job.equals(Text.NOT_SET))
{ // job given
me = true;
}
if (me)
{
FileWrapper fw = new FileWrapper("me_info.txt", "MainInfo");
allFiles.put(fw.getReadableFilename(), fw);
if (!job.getLevelOne().equals(Text.NOT_SET))
{
writeToMeFile(new MeSaveInfo(EntryType.JOB, Text.OCCUPATION_LEVEL_ONE, job.getLevelOne()));
if (!job.getLevelTwo().equals(Text.NOT_SET))
{
writeToMeFile(new MeSaveInfo(EntryType.JOB, Text.OCCUPATION_LEVEL_TWO, job.getLevelTwo()));
if (!job.getLevelThree().equals(Text.NOT_SET))
{
writeToMeFile(new MeSaveInfo(EntryType.JOB, Text.OCCUPATION_LEVEL_THREE, job.getLevelThree()));
if (!job.getLevelFour().equals(Text.NOT_SET))
{
writeToMeFile(new MeSaveInfo(EntryType.JOB, Text.OCCUPATION_LEVEL_FOUR, job.getLevelFour()));
}
}
}
}
}
if (this.selection.entrySelected(EntryType.ACTIVITY_SUMMARY))
{
FileWrapper fw = new FileWrapper("activity_summary.csv", EntryType.ACTIVITY_SUMMARY.getValue(), null,
Utils.ACTIV_SUMM_ATTR);
allFiles.put(fw.getReadableFilename(), fw);
}
if (this.selection.entrySelected(EntryType.WORKOUT))
{
for (String selectedType : selection.getWorkouts())
{
FileWrapper fw = new FileWrapper("workout_" + selectedType + ".csv", EntryType.WORKOUT.getValue(),
Utils.shortenHKStrings(selectedType), Utils.workoutAttributes);
allFiles.put(fw.getReadableFilename(), fw);
}
}
if (this.selection.entrySelected(EntryType.RECORD))
{
for (String selectedType : selection.getRecords())
{
FileWrapper fw = new FileWrapper("record_" + selectedType + ".csv", EntryType.RECORD.getValue(),
Utils.shortenHKStrings(selectedType), Utils.recordAttributes);
allFiles.put(fw.getReadableFilename(), fw);
}
}
}
catch (WrappedException e)
{
errorMessages.append(e.getReason());
errorMessages.append("\n");
e.getException().printStackTrace();
}
catch (Exception e)
{
errorMessages.append(Text.E_CREATE_TEMP_FILES);
errorMessages.append("\n");
e.printStackTrace();
}
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
{
try
{
// ME Entries not of the me tag
if (selection.entrySelected(EntryType.REGION_CODE) && qName.equalsIgnoreCase(Text.TAG_NAME_HEALTH_DATA))
{
writeToMeFile(new MeSaveInfo(EntryType.REGION_CODE, attributes.getValue(Text.TAG_ATTR_REGION_CODE)));
}
else if (selection.entrySelected(EntryType.EXPORT_DATE)
&& qName.equalsIgnoreCase(EntryType.EXPORT_DATE.getValue()))
{
writeToMeFile(new MeSaveInfo(EntryType.EXPORT_DATE, attributes.getValue(Text.TAG_ATTR_EXPORT_DATE)));
}
// ME Tag
else if (qName.equalsIgnoreCase(Text.TAG_NAME_ME_INFO))
{
for (EntryType me : EntryType.ALL_ORIGINAL_ME_ONES)
{
if (selection.entrySelected(me))
{
String dateOfBirthString = attributes.getValue(Text.TAG_ATTR_DATE_OF_BIRTH);
// first the simple ones
if (me != EntryType.DATE_OF_BIRTH)
{
String value = "";
if (me == EntryType.BIOLOGICAL_SEX)
{
value = BiologicalSex.getValue(attributes.getValue(Text.TAG_ATTR_BIOLOGICAL_SEX))
.getReadableValue();
}
else if (me == EntryType.BLOOD_TYPE)
{
value = BloodType.getValue(attributes.getValue(Text.TAG_ATTR_BLOOD_TYPE))
.getReadableValue();
}
else if (me == EntryType.SKIN_TYPE)
{
value = SkinType.getValue(attributes.getValue(Text.TAG_ATTR_SKIN_TYPE))
.getReadableValue();
}
writeToMeFile(new MeSaveInfo(me, value));
}
else if (dateOfBirthString != null && !dateOfBirthString.equals(""))
{// now the date of birth with individually selected parts
try
{
Calendar dateOfBirth = new GregorianCalendar();
dateOfBirth.setTime(Utils.formatDateOfBirth.parse(dateOfBirthString));
if (selection.birthPartSelected(Text.YEAR))
{
writeToMeFile(new MeSaveInfo(EntryType.DATE_OF_BIRTH, Text.YEAR,
dateOfBirth.get(Calendar.YEAR) + ""));
}
if (selection.birthPartSelected(Text.MONTH))
{
writeToMeFile(new MeSaveInfo(EntryType.DATE_OF_BIRTH, Text.MONTH,
"" + (dateOfBirth.get(Calendar.MONTH) + 1)));
}
if (selection.birthPartSelected(Text.DAY))
{
writeToMeFile(new MeSaveInfo(EntryType.DATE_OF_BIRTH, Text.DAY,
dateOfBirth.get(Calendar.DAY_OF_MONTH) + ""));
}
}
catch (ParseException e)
{
e.printStackTrace();
// bad luck. Do nothing if parsing error
}
}
}
}
}
// activity summaries: only one file and no special cases
else if (selection.entrySelected(EntryType.ACTIVITY_SUMMARY)
&& qName.equalsIgnoreCase(EntryType.ACTIVITY_SUMMARY.getValue()))
{
writeToFile(new GeneralSaveInfo(getValuesToAttributes(Utils.ACTIV_SUMM_ATTR, attributes),
EntryType.ACTIVITY_SUMMARY));
}
// The following ones might need completion in the endElement-method
else if (qName.equalsIgnoreCase(EntryType.WORKOUT.getValue()) && selection.workoutSelected(attributes))
{
subElementName = Utils.shortenHKStrings(attributes.getValue(Text.TAG_ATTR_WORKOUT_TYPE));
completeRow.append(getNonNestedAttributeValues(
getValuesToAttributes(Utils.workoutAttributes, attributes), Utils.workoutAttributes, true));
}
else if (qName.equalsIgnoreCase(EntryType.RECORD.getValue()) && selection.recordSelected(attributes))
{
subElementName = Utils.shortenHKStrings(attributes.getValue(Text.TAG_ATTR_RECORD_TYPE));
completeRow.append(getNonNestedAttributeValues(
getValuesToAttributes(Utils.recordAttributes, attributes), Utils.recordAttributes, true));
}
// These are now nested elements with nested attributes
else if (subElementName != null && isNestedAttribute(qName))
{
for (String tagName : nestedAttributes.keySet())
{
if (qName.equals(tagName))
{ // all entries in attributes
AttributeDataInfoTuple tuple = nestedAttributes.get(tagName);
if (tagName.equals(Text.TAG_NAME_HR_LIST))
{ // this one is completely nested
tuple.getBuilder().append("[");
}
else
{
tuple.getBuilder()
.append(getNonNestedAttributeValues(
getValuesToAttributes(tuple.getSubAttributes(), attributes),
tuple.getSubAttributes(), false));
if (!tagName.equals(Text.TAG_NAME_WORKOUT_ROUTE))
{ // workout route is nested again and isn't complete yet
tuple.getBuilder().append(",");
}
}
}
}
}
}
catch (WrappedException e)
{
errorMessages.append(e.getReason());
errorMessages.append("\n");
// TODO: alle prints entfernen
e.getException().printStackTrace();
}
}
/**
* Writes all attributes of an element up to the first nested attribute
*
* @param values attribute values to be written
* @param names attribute names to check for nesting
* @param top indicates whether this is already a nested attribute
* ({@code False}) or whether we are at top level ({@code True}
* @return The complete string for the element if there are no nested values,
* otherwise the incomplete beginning which needs to be finished with
* {@code addAllNestedAttributes}
*/
private String getNonNestedAttributeValues(String[] values, String[] names, boolean top)
{
StringBuilder res = new StringBuilder();
// Start element with a bracket
res.append("[");
for (int i = 0; i < names.length; i++)
{
if (isNestedAttribute(names[i]))
{ // stop if attribute is nested, then we need to finish at another point
// here is no bracket at the end!
return res.toString();
}
res.append(values[i]);
if (top)
{
res.append(";");
}
else
{
res.append(",");
}
}
// replace last char (, or ;) with bracket
res = res.replace(res.length() - 1, res.length(), "]");
return res.toString();
}
/**
* Determines for an attribute name whether it is a nested attribute
*
* @param attr the name to check
* @return {@code True} if it is a nested attribute, {@code False} otherwise.
*/
private boolean isNestedAttribute(String attr)
{
for (String nestedAttribute : nestedAttributes.keySet())
{
if (attr.equals(nestedAttribute))
{
return true;
}
}
return false;
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException
{
if (subElementName != null)
{ // we have a selected value which is nested / has nested attributes
if (isNestedAttribute(qName))
{ // nested case: either hr_list or workout_route
if (qName.equals(Text.TAG_NAME_HR_LIST))
{ // all entries in IBperMinutes
appendSubEntry(Text.TAG_NAME_HR_LIST, Text.TAG_NAME_IB_PER_MINUTES);
nestedAttributes.put(Text.TAG_NAME_IB_PER_MINUTES,
new AttributeDataInfoTuple(Utils.IB_PER_MINUTES_ATTR));
nestedAttributes.get(Text.TAG_NAME_HR_LIST).getBuilder().append("],");
}
if (qName.equals(Text.TAG_NAME_WORKOUT_ROUTE))
{ // last entry not in attributes! (MetaDataEntry)
appendSubEntry(Text.TAG_NAME_WORKOUT_ROUTE, Text.TAG_NAME_META_DATA_ENTRY);
nestedAttributes.put(Text.TAG_NAME_META_DATA_ENTRY,
new AttributeDataInfoTuple(Utils.META_DATA_ATTR));
nestedAttributes.get(Text.TAG_NAME_WORKOUT_ROUTE).getBuilder().append("],");
}
}
else
{ // this can only be a record or workout
// non nested ones were written in the start method, so only add the nested ones
// now
if (qName.equalsIgnoreCase(EntryType.WORKOUT.getValue()))
{
addAllNestedAttributes(completeRow, Utils.workoutAttributes);
}
else
{
addAllNestedAttributes(completeRow, Utils.recordAttributes);
}
completeRow = removeOuterBrackets(completeRow);
FileWrapper fw = allFiles.get(FileWrapper.createReadableFileName(qName, subElementName));
if (fw != null)
{
try
{
fw.getBufferedWriter().append(completeRow);
fw.getBufferedWriter().newLine();
}
catch (IOException e)
{
errorMessages.append(Text.E_WRITE_TO_TEMP_FILES);
errorMessages.append("\n");
e.printStackTrace();
}
}
resetSubAttributes();
}
}
}
/**
* Used for removing the outer Brackets in a StringBuilder, but in truth just
* removes the first and last char of it.... So be careful, it doesn't check for
* brackets!
*
* @param sb The StringBuilder to be shortened
* @return The shortened StringBuilder
*/
private StringBuilder removeOuterBrackets(StringBuilder sb)
{
sb = sb.replace(0, 1, "");
return sb.replace(sb.length() - 1, sb.length(), "");
}
/**
* Nested Attributes (which are ALLWAYS at the end of attribute lists) are here
* appended at the end of the current row
*
* @param toAppend Contains the current row which should be appended
* @param attributes The whole attribute list of the current row. But only the
* nested ones will be appended thus the non nested ones need
* to already be in the StringBuilder
*/
private void addAllNestedAttributes(StringBuilder toAppend, String[] attributes)
{
for (int i = 0; i < attributes.length; i++)
{
for (String tagName : nestedAttributes.keySet())
{
if (attributes[i].equals(tagName))
{
appendSubEntry(toAppend, tagName);
toAppend.append(";");
}
}
}
toAppend = toAppend.replace(toAppend.length() - 1, toAppend.length(), "]");
}
/**
* Appends the text of a sub entry to its containing row. All sub entries (even
* if they are empty) will be surrounded by brackets [].
*
* @param toAppendTo The containing row which should be appended
* @param nestedInfo The name of the attribute containing the sub entry
* information
*/
private void appendSubEntry(StringBuilder toAppendTo, String nestedInfo)
{
toAppendTo.append("[");
StringBuilder toAppendWith = nestedAttributes.get(nestedInfo).getBuilder();
if (toAppendWith.length() > 0) // there is an element here, so there is also a ',' too much
{
toAppendTo.append(toAppendWith.substring(0, toAppendWith.length() - 1));
}
toAppendTo.append("]");
}
/**
* Appends the text of a sub entry to its containing row. All sub entries (even
* if they are empty) will be surrounded by brackets [].
*
* @param topLevelInfo The name of the attribute which should be appended with a
* sub attribute
* @param nestedInfo The name of the attribute containing the sub entry
* information
*/
private void appendSubEntry(String topLevelInfo, String nestedInfo)
{
appendSubEntry(nestedAttributes.get(topLevelInfo).getBuilder(), nestedInfo);
}
/**
* /** Writes one line of information to a file (but not the ME File)
*
* @param info The information about what to write.
* @throws WrappedException if the file could not be written
*/
private void writeToFile(GeneralSaveInfo info) throws WrappedException
{
FileWrapper fw = allFiles.get(info.getReadableFilename());
if (fw != null)
{
BufferedWriter bout = fw.getBufferedWriter();
try
{
writeNormalRow(bout, info.getValues(), true);
bout.newLine();
}
catch (IOException e)
{
e.printStackTrace();
throw new WrappedException(e, Text.E_WRITE_TO_TEMP_FILES);
}
}
}
/**
* Writes one line of information to the me file
*
* @param info The information about what to write.
* @throws WrappedException if the file could not be written
*/
private void writeToMeFile(MeSaveInfo info) throws WrappedException
{
FileWrapper fw = allFiles.get(FileWrapper.createReadableFileName(Text.ME_FILE_DESC));
if (fw != null)
{
BufferedWriter bout = fw.getBufferedWriter();
try
{
bout.write(String.format(Text.F_ME_SAVE, info.getSaveKey(), info.getSaveValue()));
bout.newLine();
}
catch (IOException e)
{
e.printStackTrace();
throw new WrappedException(e, Text.E_WRITE_TO_TEMP_FILES);
}
}
}
/**
* Initializes the HashMap containing the nested attributes info, as well as the
* completeRow and sets the subElementname to {@code null}
*/
private void resetSubAttributes()
{
nestedAttributes = new HashMap<>();
nestedAttributes.put(Text.TAG_NAME_META_DATA_ENTRY, new AttributeDataInfoTuple(Utils.META_DATA_ATTR));
nestedAttributes.put(Text.TAG_NAME_WORKOUT_EVENT, new AttributeDataInfoTuple(Utils.WORKOUT_EVENT_ATTR));
nestedAttributes.put(Text.TAG_NAME_WORKOUT_ROUTE, new AttributeDataInfoTuple(Utils.WORKOUT_ROUTE_ATTR));
nestedAttributes.put(Text.TAG_NAME_HR_LIST, new AttributeDataInfoTuple(Utils.HR_LIST_ATTR));
nestedAttributes.put(Text.TAG_NAME_IB_PER_MINUTES, new AttributeDataInfoTuple(Utils.IB_PER_MINUTES_ATTR));
completeRow = new StringBuilder();
subElementName = null;
}
/**
* Gets the values for the given attributes and returns them in an array where
* the values are saved at the index corresponding to their keys.
*
* @param attributeName Keys / names of the attributes.
* @param values The object containing the values
* @return The described array
*/
private String[] getValuesToAttributes(String[] attributeName, Attributes values)
{
String[] res = new String[attributeName.length];
for (int i = 0; i < res.length; i++)
{
String v = values.getValue(attributeName[i]);
if(attributeName[i].equals("device")&&v!=null)
{
//Entry left out due to privacy
String device="<HKDevice:";
String actualDeviceSubstring=v.substring(v.indexOf(device),v.indexOf(">")+1);
v=v.replace(actualDeviceSubstring, "device_anonymized");
}
if (v == null)
{
res[i] = "";
}
else if (v.contains(",") || v.contains(";"))
{
res[i] = "\"" + v + "\"";
}
else
{
res[i] = v;
}
}
return res;
}
/**
* Writes a non nested row into a file
*
* @param bout The writer of the file
* @param values the values in correct order needed to fit with the header row
* which should be written
* @param top shows whether this is a "nested row" / nested attribute entry
* @throws IOException if there was an error writing
*/
private void writeNormalRow(Writer bout, String[] values, boolean top) throws IOException
{
for (int i = 0; i < values.length; i++) // for every attribute value
{
bout.write(values[i]);
if (i < values.length - 1)
{
if (top)
{
bout.write(";");
}
else
{
bout.write(",");
}
}
}
}
/**
* Returns all files written in temp directory with the readable name as key and
* the {@link File} as value. It also closes the writers and thus can only be
* used after the end of writing.
*
* @return The described value.
*/
public HashMap<String, File> getAllFiles()
{
HashMap<String, File> res = new HashMap<>();
for (String key : allFiles.keySet())
{
FileWrapper fw = allFiles.get(key);
try
{
fw.getBufferedWriter().close();
}
catch (IOException e)
{
// Stream couldn't be closed. Bad luck.
}
res.put(key, fw.getFile());
}
return res;
}
/**
* Returns all accumulated error messages.
*
* @return The described value or {@code null} if there is no error message.
*/
public String getErrorMessages()
{
if (errorMessages.length()==0)
{
return null;
}
return errorMessages.toString();
}
}