blob: 239bafa3b4d05cc2ced7046b9c3aa1346d9a2998 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014, 2015 Willink Transformations 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:
* E.D.Willink - initial API and implementation
*******************************************************************************/
package org.eclipse.ocl.xtext.base.ui.model;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl;
import org.eclipse.emf.edit.ui.util.EditUIUtil;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ocl.pivot.Model;
import org.eclipse.ocl.pivot.PivotPackage;
import org.eclipse.ocl.pivot.internal.resource.OCLASResourceFactory;
import org.eclipse.ocl.pivot.internal.resource.StandaloneProjectMap;
import org.eclipse.ocl.pivot.internal.utilities.PivotConstantsInternal;
import org.eclipse.ocl.pivot.resource.ASResource;
import org.eclipse.ocl.pivot.resource.CSResource;
import org.eclipse.ocl.pivot.utilities.ClassUtil;
import org.eclipse.ocl.xtext.base.ui.BaseUiModule;
import org.eclipse.ocl.xtext.base.ui.BaseUiPluginHelper;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.xtext.parsetree.reconstr.XtextSerializationException;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.ui.editor.model.XtextDocument;
import org.eclipse.xtext.validation.IConcreteSyntaxValidator.InvalidConcreteSyntaxException;
/**
* QVTimperativeDocumentProvider orchestrates the load and saving of optional XMI content
* externally while maintaining the serialised human friendly form internally.
*/
public abstract class BaseCSorASDocumentProvider extends BaseDocumentProvider
{
private static final Logger log = Logger.getLogger(BaseCSorASDocumentProvider.class);
public static final String PERSIST_AS_PIVOT = "pivot";
public static final String PERSIST_AS_TEXT = "text";
/**
* Representation used when loaded.
*/
protected Map<IDocument,String> loadedAsMap = new HashMap<IDocument,String>();
/**
* Delegate URI to be used when exporting, null for default.
*/
protected Map<IDocument,String> exportDelegateURIMap = new HashMap<IDocument,String>();
/**
* Representation to be used when saved.
*/
protected Map<IDocument,String> saveAsMap = new HashMap<IDocument,String>();
protected Map<IDocument, URI> uriMap = new HashMap<IDocument, URI>(); // Helper for setDocumentContent
public static @NonNull InputStream createResettableInputStream(@NonNull InputStream inputStream) throws IOException {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
byte[] buffer = new byte[16384];
int len;
while ((len = inputStream.read(buffer, 0, buffer.length)) > 0) {
outputStream.write(buffer, 0, len);
}
return new ByteArrayInputStream(outputStream.toByteArray());
}
finally {
outputStream.close();
}
}
protected void diagnoseErrors(XtextResource xtextResource, Exception e) throws CoreException {
List<Diagnostic> diagnostics = xtextResource.validateConcreteSyntax();
if (diagnostics.size() > 0) {
StringBuilder s = new StringBuilder();
s.append("Concrete Syntax validation failed");
for (Diagnostic diagnostic : diagnostics) {
s.append("\n");
s.append(diagnostic.toString());
}
throw new CoreException(new Status(IStatus.ERROR, BaseUiModule.PLUGIN_ID, s.toString(), e));
}
else {
throw new CoreException(new Status(IStatus.ERROR, BaseUiModule.PLUGIN_ID, "Failed to load", e));
}
}
protected abstract @NonNull String createTestDocument(@NonNull URI uri, @NonNull String lastSegment);
@Override
protected void doSaveDocument(IProgressMonitor monitor, Object element, IDocument document, boolean overwrite) throws CoreException {
String saveAs = saveAsMap.get(document);
if ((element instanceof IFileEditorInput) && (document instanceof BaseDocument) && !PERSIST_AS_TEXT.equals(saveAs)) {
StringWriter xmlWriter = new StringWriter();
try {
URI uri = EditUIUtil.getURI((IFileEditorInput)element);
if (uri == null) {
log.warn("No URI");
}
else if (PERSIST_AS_PIVOT.equals(saveAs)) {
((BaseDocument) document).saveAsPivot(xmlWriter);
}
else {
log.warn("Unknown saveAs '" + saveAs + "'");
}
IDocument saveDocument = new Document();
saveDocument.set(xmlWriter.toString());
super.doSaveDocument(monitor, element, saveDocument, overwrite);
loadedAsMap.put(document, saveAs);
} catch (Exception e) {
BaseUiPluginHelper helper = BaseUiPluginHelper.INSTANCE;
String title = helper.getString("_UI_SaveFailure_title", true);
String message = helper.getString("_UI_SaveFailure_message", true);
ErrorDialog.openError(null, title, message, helper.createErrorStatus(e));
monitor.setCanceled(true); // Still dirty
}
}
else {
super.doSaveDocument(monitor, element, document, overwrite);
}
}
protected abstract String getCScontentType();
protected abstract @NonNull String getFileExtension();
@Override
protected void handleElementContentChanged(IFileEditorInput fileEditorInput) {
FileInfo info= (FileInfo) getElementInfo(fileEditorInput);
if (info == null)
return;
if (info.fDocument == null) {
super.handleElementContentChanged(fileEditorInput);
}
IDocument document = info.fDocument;
String oldContent= document.get();
IStatus status= null;
try {
try {
refreshFile(fileEditorInput.getFile());
} catch (CoreException x) {
handleCoreException(x, "FileDocumentProvider.handleElementContentChanged"); //$NON-NLS-1$
}
cacheEncodingState(fileEditorInput);
setDocumentContent(document, fileEditorInput, info.fEncoding);
} catch (CoreException x) {
status= x.getStatus();
}
String newContent= document.get();
if ( !newContent.equals(oldContent)) {
// set the new content and fire content related events
fireElementContentAboutToBeReplaced(fileEditorInput);
removeUnchangedElementListeners(fileEditorInput, info);
info.fDocument.removeDocumentListener(info);
info.fDocument.set(newContent);
info.fCanBeSaved= false;
info.fModificationStamp= computeModificationStamp(fileEditorInput.getFile());
info.fStatus= status;
addUnchangedElementListeners(fileEditorInput, info);
fireElementContentReplaced(fileEditorInput);
} else {
removeUnchangedElementListeners(fileEditorInput, info);
// fires only the dirty state related event
info.fCanBeSaved= false;
info.fModificationStamp= computeModificationStamp(fileEditorInput.getFile());
info.fStatus= status;
addUnchangedElementListeners(fileEditorInput, info);
fireElementDirtyStateChanged(fileEditorInput, false);
}
}
@Override
public boolean isDeleted(Object element) {
IDocument document = getDocument(element);
String loadIsEcore = loadedAsMap.get(document);
String saveIsEcore = saveAsMap.get(document);
if ((loadIsEcore != null) && !loadIsEcore.equals(saveIsEcore)) {
return true; // Causes Save to do SaveAs
}
return super.isDeleted(element);
}
/**
* @deprecated No longer used.
*/
@Deprecated
protected boolean isXML(@NonNull InputStream inputStream) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
try {
String line = reader.readLine();
inputStream.reset();
return (line != null) && line.startsWith("<?xml");
}
finally {
reader.close();
}
}
protected boolean isXML(@NonNull InputStream inputStream, String encoding) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, encoding));
try {
String line = reader.readLine();
inputStream.reset();
return (line != null) && line.startsWith("<?xml");
}
finally {
reader.close();
}
}
@Override
protected void loadResource(XtextResource resource, String document, String encoding) throws CoreException {
assert resource != null;
getOCL().getEnvironmentFactory().adapt(resource);
super.loadResource(resource, document, encoding);
}
@Override
protected boolean setDocumentContent(IDocument document, IEditorInput editorInput, String encoding) throws CoreException {
URI uri = EditUIUtil.getURI(editorInput);
uriMap.put(document, uri);
return super.setDocumentContent(document, editorInput, encoding);
}
@Override
protected void setDocumentContent(IDocument document, InputStream inputStream, String encoding) throws CoreException {
// @NonNull String displayText = sourceText;
try {
// String xmlEncoding = URIConverter.ReadableInputStream.getEncoding(sourceText);
if (!inputStream.markSupported()) {
inputStream = createResettableInputStream(inputStream);
}
boolean isXML = isXML(inputStream, encoding);
String persistAs = PERSIST_AS_TEXT;
if (isXML) {
ResourceSet asResourceSet = getOCL().getMetamodelManager().getASResourceSet();
StandaloneProjectMap projectMap = StandaloneProjectMap.getAdapter(asResourceSet);
StandaloneProjectMap.IConflictHandler conflictHandler = StandaloneProjectMap.MapToFirstConflictHandlerWithLog.INSTANCE; //null; // FIXME
projectMap.configure(asResourceSet, StandaloneProjectMap.LoadFirstStrategy.INSTANCE, conflictHandler);
StandaloneProjectMap.IProjectDescriptor pivotPackageDescriptor = projectMap.getProjectDescriptor(PivotConstantsInternal.PLUGIN_ID);
if (pivotPackageDescriptor != null) {
pivotPackageDescriptor.configure(asResourceSet, StandaloneProjectMap.LoadBothStrategy.INSTANCE, conflictHandler);
}
URI uri = uriMap.get(document);
XMLResource xmiResource = (XMLResource) asResourceSet.getResource(uri, false);
if ((xmiResource == null) || (xmiResource.getResourceSet() == null)) { // Skip built-ins and try again as a file read.
xmiResource = (XMLResource) asResourceSet.createResource(uri, null);
}
else {
xmiResource.unload();
}
// xmiResource.load(new InputSource(new StringReader(sourceText)), null);
xmiResource.load(inputStream, null);
EcoreUtil.resolveAll(asResourceSet);
List<Resource.Diagnostic> allErrors = null;
for (Resource resource : asResourceSet.getResources()) {
List<Resource.Diagnostic> errors = resource.getErrors();
if (errors.size() > 0) {
if (allErrors == null) {
allErrors = new ArrayList<Resource.Diagnostic>();
}
allErrors.addAll(errors);
}
}
if (allErrors != null) {
StringBuilder s = new StringBuilder();
for (Resource.Diagnostic diagnostic : allErrors) {
s.append("\n");
s.append(diagnostic.toString());
}
throw new CoreException(new Status(IStatus.ERROR, BaseUiModule.PLUGIN_ID, s.toString()));
}
ASResource asResource = null;
EList<EObject> contents = xmiResource.getContents();
if (contents.size() > 0) {
EObject xmiRoot = contents.get(0);
if (xmiRoot instanceof Model) {
asResource = (ASResource) xmiResource;
persistAs = PERSIST_AS_PIVOT;
}
// FIXME general extensibility
}
if (asResource == null) {
throw new CoreException(new Status(IStatus.ERROR, BaseUiModule.PLUGIN_ID, "Failed to load"));
}
//
ResourceSetImpl csResourceSet = (ResourceSetImpl)getOCL().getResourceSet();
csResourceSet.getPackageRegistry().put(PivotPackage.eNS_URI, PivotPackage.eINSTANCE);
URI textURI = xmiResource.getURI().appendFileExtension(getFileExtension());
CSResource csResource = (CSResource) csResourceSet.getResource(textURI, false);
if (csResource == null) {
csResource = (CSResource) csResourceSet.createResource(textURI, getCScontentType());
Map<URI, Resource> map = csResourceSet.getURIResourceMap();
map.put(textURI, csResource);
csResource.setURI(xmiResource.getURI());
}
//
// ResourceSet contains
// Ecore XMI resource with *.ecore URI, possibly in URIResourceMap as *.ecore
// QVTimperative CS resource with *.ecore URI, in URIResourceMap as *.ecore.oclinecore
//
csResource.updateFrom(asResource, getOCL().getEnvironmentFactory());
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
inputStream = new ByteArrayInputStream(outputStream.toByteArray());
// StringWriter writer = new StringWriter();
try {
// csResource.save(new URIConverter.WriteableOutputStream(writer, xmlEncoding), null);
try {
csResource.save(outputStream, null);
}
catch (RuntimeException e) {
Resource csResource2 = new XMIResourceFactoryImpl().createResource(csResource.getURI().appendFileExtension("xmi"));
csResource2.getContents().addAll(csResource.getContents());
csResource2.save(outputStream, null);
csResource.getContents().addAll(csResource2.getContents());
BaseUiPluginHelper helper = BaseUiPluginHelper.INSTANCE;
String title = helper.getString("_UI_SerializationFailure_title", true);
String message = helper.getString("_UI_SerializationFailure_message", true);
Status errorStatus = helper.createErrorStatus(e);
ErrorDialog.openError(null, title, message, errorStatus);
// helper.log("2" + title, errorStatus);
// inputStream = new ByteArrayInputStream(outputStream.toByteArray());
// super.setDocumentContent(document, inputStream, encoding);
// throw e;
}
inputStream = new ByteArrayInputStream(outputStream.toByteArray());
} catch (InvalidConcreteSyntaxException e) {
diagnoseErrors((XtextResource) csResource, e);
} catch (XtextSerializationException e) {
diagnoseErrors((XtextResource) csResource, e);
}
csResource.unload();
// @SuppressWarnings("null")@NonNull String string = writer.toString();
// displayText = string;
//// CS2ASResourceAdapter resourceAdapter = ((BaseCSResource)csResource).getCS2ASAdapter();
//// resourceAdapter.dispose();
csResourceSet.getResources().remove(csResource);
// inputStream = new ByteArrayInputStream(outputStream.toByteArray());
}
// else if (sourceText.length() <= 0) { // Empty document
else if (inputStream.available() == 0) { // Empty document
URI uri = ClassUtil.nonNullState(uriMap.get(document));
Resource.Factory factory = Resource.Factory.Registry.INSTANCE.getFactory(uri);
if (factory instanceof OCLASResourceFactory) {
persistAs = PERSIST_AS_PIVOT;
}
// else if (factory instanceof UMLResourceFactoryImpl) {
// persistAs = PERSIST_AS_UML;
// }
String lastSegment = uri.trimFileExtension().lastSegment();
if (lastSegment == null) {
lastSegment = "Default";
}
String testDocument = createTestDocument(uri, lastSegment);
inputStream = new ByteArrayInputStream(testDocument.getBytes());
}
loadedAsMap.put(document, persistAs);
saveAsMap.put(document, persistAs);
// } catch (ParserException e) {
// throw new CoreException(new Status(IStatus.ERROR, OCLExamplesCommonPlugin.PLUGIN_ID, "Failed to load", e));
} catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, BaseUiModule.PLUGIN_ID, "Failed to load", e));
/* } catch (Throwable e) {
Runnable displayRefresh = new Runnable() {
@Override
public void run() {
StringWriter stringWriter = new StringWriter();
PrintWriter pw = new PrintWriter(stringWriter);
e.printStackTrace(pw);
String string = stringWriter.toString().replace("\r", "");
MessageDialog.openError(null, BaseUIMessages.LoadError_Title, string);
}
};
Display.getDefault().asyncExec(displayRefresh);
displayText = "/* Load failed * /";
*/ }
/*
* This fails to setup Xtext correctly: No state leads to NPE from EcoreUtil.resolveAll.
*
if (reload) {
final InputStream finalInputStream = inputStream;
((XtextDocument)document).modify(new IUnitOfWork<Object, XtextResource>() {
public Object exec(XtextResource state) throws Exception {
QVTimperativeDocumentProvider.super.setDocumentContent(document, finalInputStream, encoding);
return null;
}
});
}
else { */
super.setDocumentContent(document, inputStream, encoding);
// superSetDocumentText(document, displayText);
// }
}
public void setExportDelegateURI(Object element, String uri) {
exportDelegateURIMap.put(getDocument(element), uri);
}
public void setPersistAs(Object element, String persistAs) {
saveAsMap.put(getDocument(element), persistAs);
setCanSaveDocument(element);
}
protected void superDoSaveDocument(IProgressMonitor monitor, Object element, IDocument document,
boolean overwrite) throws CoreException {
super.doSaveDocument(monitor, element, document, overwrite);
}
/**
* @deprecated no longer used - does nothing - retained for API compatibility
*/
@Deprecated
protected void superSetDocumentText(@NonNull XtextDocument document, @NonNull String displayText) throws CoreException {}
protected void superSetDocumentContent(IDocument document, InputStream inputStream, String encoding) throws CoreException {
super.setDocumentContent(document, inputStream, encoding);
}
}