/******************************************************************************* * FreeQDA, a software for professional qualitative research data * analysis, such as interviews, manuscripts, journal articles, memos * and field notes. * * Copyright (C) 2011 Dirk Kitscha, Jörg große Schlarmann * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. *******************************************************************************/ package net.sf.freeqda.common; import java.io.File; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import net.sf.freeqda.bindings.document.AppliedTagType; import net.sf.freeqda.bindings.document.AppliedTagsListType; import net.sf.freeqda.bindings.document.FQDADocumentType; import net.sf.freeqda.bindings.document.FontType; import net.sf.freeqda.bindings.document.StyleRangeType; import net.sf.freeqda.bindings.project.FQDAProjectType; import net.sf.freeqda.bindings.project.TagListType; import net.sf.freeqda.bindings.project.TagType; import net.sf.freeqda.bindings.project.TextCategoryListType; import net.sf.freeqda.bindings.project.TextCategoryType; import net.sf.freeqda.bindings.project.TextListType; import net.sf.freeqda.bindings.project.TextType; import net.sf.freeqda.common.projectmanager.ProjectManager; import net.sf.freeqda.common.projectmanager.TextCategoryNode; import net.sf.freeqda.common.projectmanager.TextNode; import net.sf.freeqda.common.tagregistry.TagManager; import net.sf.freeqda.common.tagregistry.TagNode; import net.sf.freeqda.common.widget.TagableStyleRange; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.graphics.RGB; public class JAXBUtils { /** * The prefix that is used for tags in the XML file. */ protected static final String JAXB_CATEGORY_ID_PREFIX = "Category-"; //$NON-NLS-1$ /** * The prefix that is used for texts in the XML file. */ protected static final String JAXB_TEXT_ID_PREFIX = "Text-"; //$NON-NLS-1$ /** * The prefix that is used for tags in the XML file. */ protected static final String JAXB_TAG_ID_PREFIX = "Tag-"; //$NON-NLS-1$ /** * The postfix that is used for root IDs in the XML file. */ private static final String JAXB_TAG_ROOT_ID_POSTFIX = "-0"; //$NON-NLS-1$ private static final String EXCEPTION_CATEGORY_NODE_INVALID = Messages.JAXBUtils_ExceptionCategoryNodeInvalid; private static final String EXCEPTION_TEXT_NODE_INVALID = Messages.JAXBUtils_ExceptionTextNodeInvalid; private static final String EXCEPTION_TAG_NODE_INVALID = Messages.JAXBUtils_ExceptionTagNodeInvalid; private static final String EXCEPTION_NOT_A_UNIQUE_ID = Messages.JAXBUtils_ExceptionIdNotUnique; private static final String EXCEPTION_UNMARSHALING_PROJECT_DATA = Messages.JAXBUtils_ExceptionUnmarshalingProject; private static final ProjectManager PROJECT_MANAGER = ProjectManager.getInstance(); private static final TagManager TAG_REGISTRY = TagManager.getInstance(); private static final net.sf.freeqda.bindings.project.ObjectFactory PROJECT_OBJECT_FACTORY = new net.sf.freeqda.bindings.project.ObjectFactory(); private static final net.sf.freeqda.bindings.document.ObjectFactory DOCUMENT_OBJECT_FACTORY = new net.sf.freeqda.bindings.document.ObjectFactory(); public static synchronized FQDAProjectType createBindingsFromProjectData() { FQDAProjectType binding = new FQDAProjectType(); binding.setName(PROJECT_MANAGER.getProjectName()); /* * */ TagListType tagList = new TagListType(); createTagBindings(TAG_REGISTRY.getRootTag(), tagList.getTag()); binding.setTagList(tagList); /* * */ TextCategoryListType categoryList = new TextCategoryListType(); createCategoryBindings(PROJECT_MANAGER.getRootCategory(), categoryList .getTextCategory()); binding.setTextCategoryList(categoryList); /* * */ TextListType textList = new TextListType(); createTextBindings(textList.getText()); binding.setTextList(textList); return binding; } private static synchronized FQDADocumentType createDocumentBindings(StyleRange[] styleRanges, String textContent) { FQDADocumentType binding = new FQDADocumentType(); binding.setTextContent(textContent); binding.getStyleRange().addAll(createJAXBStyleRangesFromStyledText(styleRanges, 0)); return binding; } private static void createCategoryBindings(TextCategoryNode rootNode, List<TextCategoryType> categoryList) { /* * Create a parent dummy category (for parentId only) */ TextCategoryType root = new TextCategoryType(); root.setID(JAXB_CATEGORY_ID_PREFIX + rootNode.getUID()); for (GenericTreeNode<TextCategoryNode> categoryNode : rootNode .getChildren()) { /* * Create the JAXB binding for the current category node */ TextCategoryType t = new TextCategoryType(); TextCategoryNode tn = (TextCategoryNode) categoryNode; t.setID(JAXB_CATEGORY_ID_PREFIX + tn.getUID()); t.setName(tn.getName()); if (!root.getID().endsWith(JAXB_TAG_ROOT_ID_POSTFIX)) { t.setParentID(root); } categoryList.add(t); /* * call recursive */ createCategoryBindings(tn, categoryList); } } private static void createTagBindings(TagNode rootNode, List<TagType> tagList) { /* * Create a parent dummy tag (for parentId only) */ TagType root = new TagType(); root.setID(JAXB_TAG_ID_PREFIX + rootNode.getUID()); for (GenericTreeNode<TagNode> tagNode : rootNode.getChildren()) { /* * Create the JAXB binding for the current tag node */ TagType t = new TagType(); TagNode child = (TagNode) tagNode; t.setID(JAXB_TAG_ID_PREFIX + child.getUID()); t.setName(child.getName()); if (rootNode.getUID() != 0) t.setParentID(root); t.setHexRGBColor(getColorHexCodeFromRGB(child.getRGB())); tagList.add(t); /* * call recursive */ createTagBindings(child, tagList); } } private static void createTextBindings(List<TextType> textList) { HashMap<Integer, TextNode> lookupMapText = PROJECT_MANAGER.getLookupMapText(); ArrayList<Integer> textIdList = new ArrayList<Integer>(lookupMapText .keySet()); Collections.sort(textIdList); for (Integer textId : textIdList) { TextNode textNode = lookupMapText.get(textId); TextCategoryNode textCategoryNode = textNode.getCategory(); /* * create a category dummy */ TextCategoryType parentCategory = new TextCategoryType(); parentCategory.setID(JAXB_CATEGORY_ID_PREFIX + textCategoryNode.getUID()); TextType text = new TextType(); if (textCategoryNode.getUID() != 0) text.setCategoryID(parentCategory); text.setID(JAXB_TEXT_ID_PREFIX + textNode.getUID()); text.setName(textNode.getName()); text.setActivated(textNode.isActivated()); textList.add(text); } } public static final void dumpData() { System.out.println("Projectname: " + PROJECT_MANAGER.getProjectName()); //$NON-NLS-1$ HashMap<Integer, TagNode> lookupMapTag = TAG_REGISTRY.getLookupMapTag(); HashMap<Integer, TextCategoryNode> lookupMapCategory = PROJECT_MANAGER .getLookupMapCategory(); HashMap<Integer, TextNode> lookupMapText = PROJECT_MANAGER.getLookupMapText(); System.out.println("Available Tags:"); //$NON-NLS-1$ ArrayList<Integer> tagIdList = new ArrayList<Integer>(lookupMapTag .keySet()); Collections.sort(tagIdList); for (Integer tagId : tagIdList) { System.out.println(" " + tagId + " -> " + lookupMapTag.get(tagId)); //$NON-NLS-1$ //$NON-NLS-2$ } System.out.println("Available Categories:"); //$NON-NLS-1$ ArrayList<Integer> categoryIdList = new ArrayList<Integer>( lookupMapCategory.keySet()); Collections.sort(categoryIdList); for (Integer categoryId : categoryIdList) { System.out.println(" " + categoryId + " -> " //$NON-NLS-1$ //$NON-NLS-2$ + lookupMapCategory.get(categoryId)); } System.out.println("Available Texts:"); //$NON-NLS-1$ ArrayList<Integer> textIdList = new ArrayList<Integer>(lookupMapText .keySet()); Collections.sort(textIdList); for (Integer textId : textIdList) { System.out.println(" " + textId + " -> " //$NON-NLS-1$ //$NON-NLS-2$ + lookupMapText.get(textId)); } } /** * Creates an integer UID from the category id in the JAXB file. * * @param categoryId * the category id string to convert * @return an integer UID that represents the id from the JAXB file. */ private static final int getCategoryId(String categoryId) { if (categoryId.startsWith(JAXB_CATEGORY_ID_PREFIX)) { return Integer.parseInt(categoryId.replace( JAXB_CATEGORY_ID_PREFIX, StringTools.EMPTY)); } else { throw new IllegalArgumentException(MessageFormat.format(EXCEPTION_CATEGORY_NODE_INVALID, new Object[] {categoryId, JAXB_CATEGORY_ID_PREFIX})); } } private static final byte[] getColorHexCodeFromRGB(RGB color) { return new byte[] { (byte) color.red, (byte) color.green, (byte) color.blue }; } /** * Creates a RGB object from the color hex code stored in the JAXB file. * * @param colorHexCode * the color hex code string to convert * @return a RGB object that represents the color hex code from the JAXB * file. */ private static final RGB getRGBValueFromColorHexCode(byte[] colorCodes) { return new RGB(colorCodes[0] & 0xFF, colorCodes[1] & 0xFF, colorCodes[2] & 0xFF); } /** * Creates an integer UID from the category id in the JAXB file. * * @param categoryId * the category id string to convert * @return an integer UID that represents the id from the JAXB file. */ private static final int getTextUIDFromId(String textId) { if (textId.startsWith(JAXB_TEXT_ID_PREFIX)) { return Integer.parseInt(textId.replace( JAXB_TEXT_ID_PREFIX, StringTools.EMPTY)); } else { throw new IllegalArgumentException(MessageFormat.format(EXCEPTION_TEXT_NODE_INVALID, new Object[] {textId, JAXB_TEXT_ID_PREFIX})); } } /** * Creates an integer UID from the tag id in the JAXB file. * * @param tagId * the tag id string to convert * @return an integer UID that represents the id from the JAXB file. */ private static final int getUIDFromTagId(String tagId) { if (tagId.startsWith(JAXB_TAG_ID_PREFIX)) { return Integer.parseInt(tagId.replace( JAXB_TAG_ID_PREFIX, StringTools.EMPTY)); } else { throw new IllegalArgumentException(MessageFormat.format(EXCEPTION_TAG_NODE_INVALID, new Object[] {tagId, JAXB_TAG_ID_PREFIX})); } } /** * Initialises the project from the JAXB bindings * * @param project */ private synchronized static void initProjectManager(FQDAProjectType project, File projectFile) { PROJECT_MANAGER.reset(); PROJECT_MANAGER.setProjectName(project.getName()); PROJECT_MANAGER.setProjectFile(projectFile); HashMap<Integer, TagNode> lookupMapTag = TAG_REGISTRY.getLookupMapTag(); HashMap<Integer, TextCategoryNode> lookupMapCategory = PROJECT_MANAGER .getLookupMapCategory(); HashMap<Integer, TextNode> lookupMapText = PROJECT_MANAGER.getLookupMapText(); /* * First run attaches only "root" tags to our real root node */ for (TagType aTag : project.getTagList().getTag()) { /* * Create a TagNode object from the JAXB tag object */ TagNode currentTag = new TagNode(aTag.getName(), getRGBValueFromColorHexCode(aTag.getHexRGBColor()), getUIDFromTagId(aTag.getID())); /* * Adapt the highest UID if the current UID is greater than the * highest UID found so far */ TAG_REGISTRY.updateHighestTagUID(currentTag.getUID()); /* * tag ids have to be unique. If we see the same tag id here more * than once then the data is inconsistent! //TODO Handle data * inconsistency in TagManager */ if (lookupMapTag.containsKey(currentTag.getUID())) { throw new RuntimeException(MessageFormat.format(EXCEPTION_NOT_A_UNIQUE_ID, new Object[] {currentTag})); } /* * The current Tag is unique. Store it in the lookup map */ lookupMapTag.put(currentTag.getUID(), currentTag); /* * Tags without a parent tag are "root tags". These tags are * appended to the TagManager's ROOT_TAG object. * * In the second run the TAG_LOOKUP_MAP is filled and we can attach * all non-root tags to their parent tags. */ if (aTag.getParentID() == null) { /* * tag has no parent -> tag is a root tag */ TAG_REGISTRY.getRootTag().addChild(currentTag); } } /* * Second run attaches all "non-root" tags to their parent tag */ for (TagType aTag : project.getTagList().getTag()) { /* * get the TagNode object from the TAG_LOOKUP_MAP */ TagNode currentTag = lookupMapTag .get(getUIDFromTagId(aTag.getID())); /* * Process only tags with a parent id (non-root tags). Retrieve the * parent tag from the TAG_LOOKUP_MAP. */ if (aTag.getParentID() != null) { TagNode parentTag = lookupMapTag .get(getUIDFromTagId(((TagType) aTag.getParentID()).getID())); /* * attach this tag to his parent tag. */ parentTag.addChild(currentTag); } } /* * First run attaches only "root" tags to our real root node */ for (TextCategoryType aCategory : project.getTextCategoryList() .getTextCategory()) { /* * Create a TagNode object from the JAXB tag object */ TextCategoryNode currentCategory = new TextCategoryNode(aCategory .getName(), getCategoryId(aCategory.getID())); /* * Adapt the highest UID if the current UID is greater than the * highest UID found so far */ PROJECT_MANAGER.updateHighestCategoryUID(currentCategory.getUID()); /* * tag ids have to be unique. If we see the same tag id here more * than once then the data is inconsistent! //TODO Handle data * inconsistency in TagManager */ if (lookupMapCategory.containsKey(currentCategory.getUID())) { throw new RuntimeException(MessageFormat.format(EXCEPTION_NOT_A_UNIQUE_ID, new Object[] {currentCategory})); } /* * The current Tag is unique. Store it in the lookup map */ lookupMapCategory.put(currentCategory.getUID(), currentCategory); /* * Tags without a parent tag are "root tags". These tags are * appended to the TagManager's ROOT_TAG object. * * In the second run the TAG_LOOKUP_MAP is filled and we can attach * all non-root tags to their parent tags. */ if (aCategory.getParentID() == null) { /* * tag has no parent -> tag is a root tag */ PROJECT_MANAGER.getRootCategory().addChild(currentCategory); } } /* * Second run attaches all "non-root" categories to their parent * category */ for (TextCategoryType aCategory : project.getTextCategoryList() .getTextCategory()) { /* * get the TagNode object from the TAG_LOOKUP_MAP */ TextCategoryNode currentCategory = lookupMapCategory .get(getCategoryId(aCategory.getID())); /* * Process only tags with a parent id (non-root tags). Retrieve the * parent tag from the TAG_LOOKUP_MAP. */ if (aCategory.getParentID() != null) { TextCategoryNode parentCategory = lookupMapCategory .get(getCategoryId(((TextCategoryType) aCategory .getParentID()).getID())); /* * attach this tag to his parent tag. */ parentCategory.addChild(currentCategory); } } for (TextType text : project.getTextList().getText()) { TextNode currentTextNode = new TextNode(text.getName(), getTextUIDFromId(text.getID()), text.isActivated() != null ? text.isActivated() : false); /* * Adapt the highest UID if the current UID is greater than the * highest UID found so far */ PROJECT_MANAGER.updateHighestTextUID(currentTextNode.getUID()); /* * load data into the text node */ try { FQDADocumentType documentData = loadDocument(getFileForTextNode(currentTextNode)); currentTextNode.setStyleRanges(createTagableStyleRanges(documentData.getStyleRange())); currentTextNode.setTextContent(documentData.getTextContent()); } catch (IOException e) { //FIXME handle exception (project file could not be loaded)! e.printStackTrace(); } currentTextNode.reset(); /* * Assign the text node to its parent node */ TextCategoryNode parentCategoryNode = null; if (text.getCategoryID() != null) { parentCategoryNode = lookupMapCategory .get(getCategoryId(((TextCategoryType) text.getCategoryID()) .getID())); currentTextNode.setCategory(parentCategoryNode); parentCategoryNode.addTextFile(currentTextNode); } else { currentTextNode.setCategory(PROJECT_MANAGER.getRootCategory()); PROJECT_MANAGER.getRootCategory().addTextFile(currentTextNode); } lookupMapText.put(currentTextNode.getUID(), currentTextNode); currentTextNode.updateCodeStats(); } for (TagNode tagNode: lookupMapTag.values()) { tagNode.updateCodeStats(); } PROJECT_MANAGER.setLookupMapCategory(lookupMapCategory); PROJECT_MANAGER.setLookupMapText(lookupMapText); TAG_REGISTRY.setLookupMapTag(lookupMapTag); TAG_REGISTRY.updateCodeStats(); } public static File getFileForTextNode(TextNode textNode) { return new File(PROJECT_MANAGER.getProjectFile().getParentFile(), ProjectManager.FQDA_FILE_COMMON_NAME+textNode.getUID()+ProjectManager.FQDA_FILE_COMMON_SUFFIX); } public static void loadProject(File projectFile) throws IOException { String packageName = FQDAProjectType.class.getPackage().getName(); try { JAXBContext jc = JAXBContext.newInstance(packageName); Unmarshaller u = jc.createUnmarshaller(); JAXBElement<?> e = (JAXBElement<?>) u.unmarshal(projectFile); FQDAProjectType project = (FQDAProjectType) e.getValue(); initProjectManager(project, projectFile); } catch (JAXBException e) { PROJECT_MANAGER.reset(); throw new IOException(EXCEPTION_UNMARSHALING_PROJECT_DATA, e); } } public static void saveProject(File projectFile) throws IOException { FQDAProjectType project2Save = JAXBUtils.createBindingsFromProjectData(); String packageName = FQDAProjectType.class.getPackage().getName(); try { JAXBContext jc = JAXBContext.newInstance(packageName); Marshaller m = jc.createMarshaller(); m.marshal(PROJECT_OBJECT_FACTORY.createFQDAProject(project2Save), projectFile); } catch (JAXBException e) { throw new IOException(EXCEPTION_UNMARSHALING_PROJECT_DATA, e); } } private static FQDADocumentType loadDocument(File documentFile) throws IOException { String packageName = FQDADocumentType.class.getPackage().getName(); try { JAXBContext jc = JAXBContext.newInstance(packageName); Unmarshaller u = jc.createUnmarshaller(); JAXBElement<?> e = (JAXBElement<?>) u.unmarshal(documentFile); FQDADocumentType document = (FQDADocumentType) e.getValue(); return document; } catch (JAXBException e) { PROJECT_MANAGER.reset(); throw new IOException(EXCEPTION_UNMARSHALING_PROJECT_DATA, e); } } private static void saveDocument(FQDADocumentType document, File destinationFile) throws IOException { String packageName = FQDADocumentType.class.getPackage().getName(); try { JAXBContext jc = JAXBContext.newInstance(packageName); Marshaller m = jc.createMarshaller(); m.marshal(DOCUMENT_OBJECT_FACTORY.createFQDADocument(document), destinationFile); } catch (JAXBException e) { throw new IOException(EXCEPTION_UNMARSHALING_PROJECT_DATA, e); } } public static void saveDocument(StyleRange[] styleRanges, String textContent, File destinationFile) throws IOException { FQDADocumentType document2Save = JAXBUtils.createDocumentBindings(styleRanges, textContent); saveDocument(document2Save, destinationFile); } private static TagableStyleRange[] createTagableStyleRanges(List<StyleRangeType> styleRangeList) { /* * initialise StyleRanges */ TagableStyleRange[] res = new TagableStyleRange[styleRangeList.size()]; for (int n=0; n<styleRangeList.size(); n++) { StyleRangeType range = styleRangeList.get(n); TagableStyleRange styleRange = new TagableStyleRange(); styleRange.start = range.getStart(); styleRange.length = range.getLength(); if (range.getFont() != null) { styleRange.fontStyle = range.getFont().getFontStyle(); } if (range.getTagList() != null) { for (AppliedTagType tag: range.getTagList().getTag()) { TagNode tagNode = TAG_REGISTRY.getLookupMapTag().get(tag.getID()); styleRange.addTag(tagNode); } } res[n] = styleRange; } return res; } private static synchronized List<StyleRangeType> createJAXBStyleRangesFromStyledText(StyleRange[] styleRanges, int characterOffset) { List<StyleRangeType> styleList = new LinkedList<StyleRangeType>(); for (StyleRange sr: styleRanges) { TagableStyleRange range = (TagableStyleRange) sr; StyleRangeType bindingStyle = new StyleRangeType(); bindingStyle.setStart(range.start + characterOffset); bindingStyle.setLength(range.length); if (range.fontStyle != SWT.NORMAL) { FontType fontType = new FontType(); fontType.setFontStyle(range.fontStyle); bindingStyle.setFont(fontType); } TagNode[] appliedTags = range.getTags(); if (appliedTags != null) { AppliedTagsListType appliedTagListType = new AppliedTagsListType(); List<AppliedTagType> appliedTagsList = appliedTagListType.getTag(); for (TagNode tag: appliedTags) { AppliedTagType bindingTag = new AppliedTagType(); bindingTag.setID(tag.getUID()); appliedTagsList.add(bindingTag); } bindingStyle.setTagList(appliedTagListType); } styleList.add(bindingStyle); } return styleList; } }