//------------------------------------------------------------------------------ // Copyright (c) 2005, 2006 IBM Corporation and others. // All rights reserved. This program and the accompanying materials // are made available under the terms of the Eclipse Public License v1.0 // which accompanies this distribution, and is available at // http://www.eclipse.org/legal/epl-v10.html // // Contributors: // IBM Corporation - initial implementation //------------------------------------------------------------------------------ package org.eclipse.epf.persistence; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.eclipse.emf.common.CommonPlugin; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.InternalEObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.util.ExtendedMetaData; import org.eclipse.emf.ecore.xmi.XMLHelper; import org.eclipse.emf.ecore.xmi.XMLResource; import org.eclipse.emf.ecore.xmi.impl.XMISaveImpl; import org.eclipse.epf.common.service.versioning.EPFVersions; import org.eclipse.epf.common.service.versioning.VersionUtil; import org.eclipse.epf.resourcemanager.ResourceManager; import org.eclipse.epf.uma.MethodElement; /** * XMLSave implementation for library XMI persistence * * @author Phong Nguyen Le * @since 1.0 */ public class MultiFileXMISaveImpl extends XMISaveImpl { public static final String SAVE_SEPARATELY_CLASS_SET = "SAVE_SEPARATELY_CLASS_SET"; //$NON-NLS-1$ /** * Save options to force saving all resources in the resource set, whether * they have been modified or unchanged. Its value must be a string "true" * or "false" */ public static final String SAVE_ALL = "SAVE_ALL"; //$NON-NLS-1$ /** * Save option to save objects of the save type together in the same file if * MultiFileUtil.createFileURI() returns the same URI for them. */ public static final String SAVE_TOGETHER_CLASS_SET = "SAVE_TOGETHER_CLASS_SET"; //$NON-NLS-1$ public static final String BACK_UP_BEFORE_SAVE = "BACK_UP_BEFORE_SAVE"; //$NON-NLS-1$ public static final String DISCARD_UNRESOLVED_REFERENCES = "DISCARD_UNRESOLVED_REFERENCES"; //$NON-NLS-1$ static final String MODIFIED_RESOURCE_SET = "MODIFIED_RESOURCE_SET"; //$NON-NLS-1$ /** * Save option to specify a TxRecord to log the transactional data for * fail-safe persistence * * @see org.eclipse.epf.uma.util.IFileBasedLibraryPersister.FailSafeMethodLibraryPersister * @see TxRecord */ static final String TX_RECORD = "TX_RECORD"; //$NON-NLS-1$ public static final String MULTI_FILE = "MULTI_FILE"; //$NON-NLS-1$ /** * Save option to refresh the workspace when new resource is created and * saved. Its value must be a string "true" or "false" */ public static final String REFRESH_NEW_RESOURCE = "REFRESH_NEW_RESOURCE"; //$NON-NLS-1$ /** * Save option to check the resource for modifiable before saving it. Its * value must be a string "true" or "false" */ public static final String CHECK_MODIFY = "CHECK_MODIFY"; //$NON-NLS-1$ // private Resource resource; private Set saveSeparatelyClassSet; private Map options; /** * @param helper */ public MultiFileXMISaveImpl(XMLHelper helper) { super(helper); } XMLHelper getXMLHelper() { return helper; } /* * (non-Javadoc) * * @see org.eclipse.emf.ecore.xmi.impl.XMLSaveImpl#init(org.eclipse.emf.ecore.xmi.XMLResource, * java.util.Map) */ protected void init(XMLResource resource, Map opts) { super.init(resource, opts); // if (escape != null) { // // use MyEscape to not escape whitespaces // // // escape = new MyEscape(); // } saveSeparatelyClassSet = (Set) opts.get(SAVE_SEPARATELY_CLASS_SET); options = opts; ResourceSet resourceSet = resource.getResourceSet(); if (resourceSet == null) { resourceSet = new MultiFileResourceSetImpl(); resourceSet.getResources().add(resource); } } boolean canSaveSeparately(Object obj) { return MultiFileSaveUtil.hasOwnResource(obj, saveSeparatelyClassSet); } private String getUmaHREF(Resource resource, InternalEObject o) { if (o.eIsProxy() && o.eProxyURI().scheme().equals(MultiFileURIConverter.SCHEME)) { return o.eProxyURI().toString(); } String href = null; if (o instanceof MethodElement) { href = helper.getHREF(o); } if (href == null) { PersistencePlugin.getDefault().getLogger().logError("Could not find resource descriptor for resource " + resource + "\n object: " + o); //$NON-NLS-1$ //$NON-NLS-2$ return MultiFileSaveUtil.getHREF(resource, o); } return href; } /* * (non-Javadoc) * * @see org.eclipse.emf.ecore.xmi.impl.XMLSaveImpl#saveHref(org.eclipse.emf.ecore.EObject, * org.eclipse.emf.ecore.EStructuralFeature) */ protected void saveHref(EObject remote, EStructuralFeature f) { if (f instanceof EReference && ((EReference) f).isContainment() && !toDOM) { // if this HREF is a contained element, save its ID attribute so the // proxy can be cached in the // GUIDToElementMap for temporary use before it can be resolved. // This helps improve loading time. // String href = helper.getHREF(remote); if (href != null) { if (escapeURI != null) { href = escapeURI.convert(href); } String name = helper.getQName(f); doc.startElement(name); EClass eClass = remote.eClass(); EClass expectedType = (EClass) f.getEType(); if (saveTypeInfo ? xmlTypeInfo.shouldSaveType(eClass, expectedType, f) : eClass != expectedType && expectedType.isAbstract()) { saveTypeAttribute(eClass); } String id = helper.getID(remote); if (id != null) { doc.addAttribute(idAttributeName, id); } doc.addAttribute(XMLResource.HREF, href); if (eObjectToExtensionMap != null) { processAttributeExtensions(remote); if (processElementExtensions(remote)) { doc.endElement(); } else { doc.endEmptyElement(); } } else { doc.endEmptyElement(); } } return; } super.saveHref(remote, f); } /* * (non-Javadoc) * * @see org.eclipse.emf.ecore.xmi.impl.XMLSaveImpl#saveFeatures(org.eclipse.emf.ecore.EObject) */ protected boolean saveFeatures(EObject o) { try { MultiFileXMIResourceImpl resource = (MultiFileXMIResourceImpl) o .eResource(); if (resource != null) { EObject container = o.eContainer(); InternalEObject internalEObject = ((InternalEObject) o); if (o instanceof MethodElement && canSaveSeparately(o) && container != null) { boolean containsNewFeature = (resource == container .eResource()); MethodElement e = (MethodElement) o; URI uri = MultiFileSaveUtil.createURI(e, resource.getResourceSet()); if (containsNewFeature) { if (!internalEObject.eIsProxy()) { resource = (MultiFileXMIResourceImpl) MultiFileSaveUtil .save(o, uri, options); } } else { // check if there is a ResourceDescriptor for this // element // create a new one if needed // ResourceManager resMgr = MultiFileSaveUtil .getResourceManager(container.eResource()); if (resMgr != null && resMgr.getResourceDescriptor(e.getGuid()) == null) { // make sure that no other ResourceManager has a // ResourceDescriptor for this element // MultiFileURIConverter uriConverter = (MultiFileURIConverter) resource .getResourceSet().getURIConverter(); if (uriConverter .findResourceDescriptor(e.getGuid()) == null) { MultiFileSaveUtil.registerWithResourceManager( resMgr, e, resource.getFinalURI()); } } } String href = getUmaHREF(resource, internalEObject); doc.addAttribute(XMLResource.HREF, href); endSaveFeatures(o, 0, null); return true; } if (o instanceof ResourceManager && helper.getResource() != resource) { String href = getUmaHREF(resource, internalEObject); doc.addAttribute(XMLResource.HREF, href); endSaveFeatures(o, 0, null); return true; } } return super.saveFeatures(o); } catch (RuntimeException e) { CommonPlugin.INSTANCE.log(e); if (MultiFileSaveUtil.DEBUG) { e.printStackTrace(); System.err.println("ERROR saving feature: " + o); //$NON-NLS-1$ System.err.println(" Feature resource: " + o.eResource()); //$NON-NLS-1$ } throw e; } } /* (non-Javadoc) * @see org.eclipse.emf.ecore.xmi.impl.XMISaveImpl#addNamespaceDeclarations() */ public void addNamespaceDeclarations() { super.addNamespaceDeclarations(); if (!toDOM) { // save tool version info // for (Iterator iter = VersionUtil.getAllToolIDs().iterator();iter.hasNext();) { String toolID = (String)iter.next(); EPFVersions epfVersions = VersionUtil.getVersions(toolID); String nsUri = epfVersions.getNsURI(); doc.addAttribute(ExtendedMetaData.XMLNS_PREFIX + ":" + toolID, nsUri); //$NON-NLS-1$ String toolVersion = epfVersions.getMinToolVersionForCurrentLibraryVersion().getToolVersion().toString(); doc.addAttribute(toolID + ":version", toolVersion); //$NON-NLS-1$ } } } public static class MyEscape extends Escape { // EMF 2.2 improved Escape a lot so the override this class to add our code is no longer needed // except to make it public public MyEscape() { setMappingLimit(0x10FFFF); } } // TODO: needs to revisit this class periodically since its base class might be updated a // lot in EMF. // public static class MyEscape extends Escape { // private static final int MAX_UTF_MAPPABLE_CODEPOINT = 0x10FFFF; // // protected final char[] BLANK = {}; // // private boolean allowControlCharacters; // // public MyEscape() { // super(); // } // // @Override // public void setAllowControlCharacters(boolean allowControlCharacters) { // this.allowControlCharacters = allowControlCharacters; // } // // /* // * Convert attribute values: // * & to & // * < to < // * " to " // * \t to // * \n to // * \r to // * // * > to > (this is used for XLIFF's) // */ // public String convert(String input) // { // boolean changed = false; // int inputLength = input.length(); // grow(inputLength); // int outputPos = 0; // int inputPos = 0; // char ch = 0; // while (inputLength-- > 0) // { // ch = input.charAt(inputPos++); // value[outputPos]; // switch (ch) // { // case 0x1: // case 0x2: // case 0x3: // case 0x4: // case 0x5: // case 0x6: // case 0x7: // case 0x8: // case 0xB: // case 0xC: // case 0xE: // case 0xF: // case 0x10: // case 0x11: // case 0x12: // case 0x13: // case 0x14: // case 0x15: // case 0x16: // case 0x17: // case 0x18: // case 0x19: // case 0x1A: // case 0x1B: // case 0x1C: // case 0x1D: // case 0x1E: // case 0x1F: // { // if (allowControlCharacters) // { // outputPos = replaceChars(outputPos, CONTROL_CHARACTERS[ch], inputLength); // changed = true; // } // else // { // throw new RuntimeException("An invalid XML character (Unicode: 0x" + Integer.toHexString(ch) + ") was found in the element content:" + input); // } // break; // } // case '&': // { // outputPos = replaceChars(outputPos, AMP, inputLength); // changed = true; // break; // } // case '<': // { // outputPos = replaceChars(outputPos, LESS, inputLength); // changed = true; // break; // } // // begin new code // case '>': // { // outputPos = replaceChars(outputPos, GREATER, inputLength); // changed = true; // break; // } // // end new code // case '"': // { // outputPos = replaceChars(outputPos, QUOTE, inputLength); // changed = true; // break; // } // case '\n': // { // outputPos = replaceChars(outputPos, LF, inputLength); // changed = true; // break; // } // case '\r': // { // outputPos = replaceChars(outputPos, CR, inputLength); // changed = true; // break; // } // case '\t': // { // outputPos = replaceChars(outputPos, TAB, inputLength); // changed = true; // break; // } // default: // { // if (!XMLChar.isValid(ch)) // { // if (XMLChar.isHighSurrogate(ch)) // { // char high = ch; // if (inputLength-- > 0) // { // ch = input.charAt(inputPos++); // if (XMLChar.isLowSurrogate(ch)) // { // if (mappableLimit == MAX_UTF_MAPPABLE_CODEPOINT) // { // // Every codepoint is supported! // value[outputPos++] = high; // value[outputPos++] = ch; // } // else // { // // Produce the supplemental character as an entity // outputPos = replaceChars(outputPos, ("&#x" + Integer.toHexString(XMLChar.supplemental(high, ch)) + ";").toCharArray(), inputLength); // changed = true; // } // break; // } // throw new RuntimeException("An invalid low surrogate character (Unicode: 0x" + Integer.toHexString(ch) + ") was found in the element content:" + input); // } // else // { // throw new RuntimeException("An unpaired high surrogate character (Unicode: 0x" + Integer.toHexString(ch) + ") was found in the element content:" + input); // } // } // else // { // throw new RuntimeException("An invalid XML character (Unicode: 0x" + Integer.toHexString(ch) + ") was found in the element content:" + input); // } // } // else // { // // Normal (BMP) unicode codepoint. See if we know for a fact that the encoding supports it: // if (ch <= mappableLimit) // { // value[outputPos++] = ch; // } // else // { // // We not sure the encoding supports this codepoint, so we write it as a character entity reference. // outputPos = replaceChars(outputPos, ("&#x" + Integer.toHexString(ch) + ";").toCharArray(), inputLength); // changed = true; // } // } // break; // } // } // } // return changed ? new String(value, 0, outputPos) : input; // } // // /* // * Convert element values: // * & to & // * < to < // * " to " // * \n to line separator // * // * \r to blank; // * > to > (this is used for XLIFF's) // */ // public String convertText(String input) // { // boolean changed = false; // int inputLength = input.length(); // grow(inputLength); // int outputPos = 0; // int inputPos = 0; // char ch; // while (inputLength-- > 0) // { // ch = input.charAt(inputPos++); // value[outputPos]; // switch (ch) // { // case 0x1: // case 0x2: // case 0x3: // case 0x4: // case 0x5: // case 0x6: // case 0x7: // case 0x8: // case 0xB: // case 0xC: // case 0xE: // case 0xF: // case 0x10: // case 0x11: // case 0x12: // case 0x13: // case 0x14: // case 0x15: // case 0x16: // case 0x17: // case 0x18: // case 0x19: // case 0x1A: // case 0x1B: // case 0x1C: // case 0x1D: // case 0x1E: // case 0x1F: // { // if (allowControlCharacters) // { // outputPos = replaceChars(outputPos, CONTROL_CHARACTERS[ch], inputLength); // changed = true; // } // else // { // throw new RuntimeException("An invalid XML character (Unicode: 0x" + Integer.toHexString(ch) + ") was found in the element content:" + input); // } // break; // } // case '&': // { // outputPos = replaceChars(outputPos, AMP, inputLength); // changed = true; // break; // } // case '<': // { // outputPos = replaceChars(outputPos, LESS, inputLength); // changed = true; // break; // } // case '"': // { // outputPos = replaceChars(outputPos, QUOTE, inputLength); // changed = true; // break; // } // case '\n': // { // outputPos = replaceChars(outputPos, LINE_FEED, inputLength); // changed = true; // break; // } // // begin modified // case '\r': // { // outputPos = replaceChars(outputPos, BLANK, inputLength); // changed = true; // break; // } // case '>': // { //// if (inputPos >= 3 && input.charAt(inputPos - 2) == ']' && input.charAt(inputPos - 3) == ']') //// { //// outputPos = replaceChars(outputPos, GREATER, inputLength); //// changed = true; //// break; //// } //// // continue with default processing // outputPos = replaceChars(outputPos, GREATER, inputLength); // changed = true; // break; // } // // end modified // default: // { // if (!XMLChar.isValid(ch)) // { // if (XMLChar.isHighSurrogate(ch)) // { // char high = ch; // if (inputLength-- > 0) // { // ch = input.charAt(inputPos++); // if (XMLChar.isLowSurrogate(ch)) // { // if (mappableLimit == MAX_UTF_MAPPABLE_CODEPOINT) // { // // Every codepoint is supported! // value[outputPos++] = high; // value[outputPos++] = ch; // } // else // { // // Produce the supplemental character as an entity // outputPos = replaceChars(outputPos, ("&#x" + Integer.toHexString(XMLChar.supplemental(high, ch)) + ";").toCharArray(), inputLength); // changed = true; // } // break; // } // throw new RuntimeException("An invalid low surrogate character (Unicode: 0x" + Integer.toHexString(ch) + ") was found in the element content:" + input); // } // else // { // throw new RuntimeException("An unpaired high surrogate character (Unicode: 0x" + Integer.toHexString(ch) + ") was found in the element content:" + input); // } // } // else // { // throw new RuntimeException("An invalid XML character (Unicode: 0x" + Integer.toHexString(ch) + ") was found in the element content:" + input); // } // } // else // { // // Normal (BMP) unicode codepoint. See if we know for a fact that the encoding supports it: // if (ch <= mappableLimit) // { // value[outputPos++] = ch; // } // else // { // // We not sure the encoding supports this codepoint, so we write it as a character entity reference. // outputPos = replaceChars(outputPos, ("&#x" + Integer.toHexString(ch) + ";").toCharArray(), inputLength); // changed = true; // } // } // break; // } // } // } // return changed ? new String(value, 0, outputPos) : input; // } // // } public static boolean checkModifyRequired(Map options) { Object opt = options.get(MultiFileXMISaveImpl.CHECK_MODIFY); return opt != null ? Boolean.valueOf(opt.toString()).booleanValue() : false; } }