| //------------------------------------------------------------------------------ |
| // 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.serviceability.EPFVersions; |
| import org.eclipse.epf.common.serviceability.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 ignore 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 |
| .createFileURI((MethodElement) o); |
| 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$ |
| } |
| } |
| } |
| |
| /* |
| * protected String getDatatypeValue(Object value, EStructuralFeature f, |
| * boolean isAttribute) { if (value != null && value instanceof String) { |
| * String str = (String)value; if (str.indexOf("\r") > 0) { str = |
| * str.replaceAll("\r", ""); return super.getDatatypeValue(str, f, |
| * isAttribute); } }; return super.getDatatypeValue(value, f, isAttribute); } |
| */ |
| |
| // TODO: needs to revisit this class since its base class has been updated a |
| // lot in EMF 2.2 |
| public static class MyEscape extends Escape { |
| protected final char[] GREATER = { '&', 'g', 't', ';' }; |
| |
| protected final char[] BLANK = {}; |
| |
| public MyEscape() { |
| super(); |
| } |
| |
| /* |
| * Converts: & to & < to < " to " \t to 	 \n to 
 \r |
| * to 
 |
| */ |
| public String convert(String input) { |
| boolean changed = false; |
| int inputLength = input.length(); |
| grow(inputLength); |
| input.getChars(0, inputLength, value, 0); |
| int pos = 0; |
| char ch = 0; |
| while (inputLength-- > 0) { |
| ch = value[pos]; |
| switch (ch) { |
| case '&': |
| pos = replace(pos, AMP, inputLength); |
| changed = true; |
| break; |
| case '<': |
| pos = replace(pos, LESS, inputLength); |
| changed = true; |
| break; |
| case '>': |
| pos = replace(pos, GREATER, inputLength); |
| changed = true; |
| break; |
| case '"': |
| pos = replace(pos, QUOTE, inputLength); |
| changed = true; |
| break; |
| default: |
| /* |
| * if (!XMLChar.isValid(ch)) { throw new |
| * RuntimeException("An invalid XML character (Unicode: 0x" + |
| * Integer.toHexString(ch)+") was found in the element |
| * content:" +input); } |
| */ |
| pos++; |
| break; |
| } |
| } |
| return changed ? new String(value, 0, pos) : input; |
| } |
| |
| /** |
| * Converts: & to & < to < > to > " to " \n to |
| * platform-dependent line breaking character |
| */ |
| public String convertText(String input) { |
| boolean changed = false; |
| int inputLength = input.length(); |
| grow(inputLength); |
| input.getChars(0, inputLength, value, 0); |
| int pos = 0; |
| while (inputLength-- > 0) { |
| switch (value[pos]) { |
| case '&': |
| pos = replace(pos, AMP, inputLength); |
| changed = true; |
| break; |
| case '<': |
| pos = replace(pos, LESS, inputLength); |
| changed = true; |
| break; |
| case '>': |
| pos = replace(pos, GREATER, inputLength); |
| changed = true; |
| break; |
| case '"': |
| pos = replace(pos, QUOTE, inputLength); |
| changed = true; |
| break; |
| case '\r': |
| pos = replace(pos, BLANK, inputLength); |
| changed = true; |
| break; |
| case '\n': |
| pos = replace(pos, LINE_FEED, inputLength); |
| changed = true; |
| break; |
| default: |
| pos++; |
| break; |
| } |
| } |
| return changed ? new String(value, 0, pos) : input; |
| } |
| |
| /* |
| * Taken from XMLSaveImpl$Escape.convertText, and added conversion of > |
| * This is used for XLIFF's - change it's name so it doesn't impact EMF |
| * saving Convert: & to & < to < " to " \n to line separator |
| */ |
| public String myConvertText(String input) { |
| boolean changed = false; |
| int inputLength = input.length(); |
| grow(inputLength); |
| input.getChars(0, inputLength, value, 0); |
| int pos = 0; |
| while (inputLength-- > 0) { |
| switch (value[pos]) { |
| case '&': |
| pos = replace(pos, AMP, inputLength); |
| changed = true; |
| break; |
| case '<': |
| pos = replace(pos, LESS, inputLength); |
| changed = true; |
| break; |
| case '>': |
| pos = replace(pos, GREATER, inputLength); |
| changed = true; |
| break; |
| case '"': |
| pos = replace(pos, QUOTE, inputLength); |
| changed = true; |
| break; |
| case '\n': { |
| pos = replace(pos, LINE_FEED, inputLength); |
| changed = true; |
| break; |
| } |
| default: |
| pos++; |
| break; |
| } |
| } |
| |
| return changed ? new String(value, 0, pos) : input; |
| } |
| } |
| |
| public static boolean checkModifyRequired(Map options) { |
| Object opt = options.get(MultiFileXMISaveImpl.CHECK_MODIFY); |
| return opt != null ? Boolean.valueOf(opt.toString()).booleanValue() |
| : false; |
| } |
| } |