package org.objectstyle.wolips.wodclipse.core.woo; import static org.objectstyle.wolips.baseforplugins.util.CharSetUtils.ENCODING_UTF8; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.core.search.SearchPattern; import org.eclipse.jface.operation.IRunnableContext; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IFileEditorInput; import org.objectstyle.woenvironment.plist.PropertyListParserException; import org.objectstyle.woenvironment.plist.WOLPropertyListSerialization; import org.objectstyle.wolips.baseforplugins.util.CharSetUtils; import org.objectstyle.wolips.bindings.Activator; import org.objectstyle.wolips.bindings.preferences.PreferenceConstants; import org.objectstyle.wolips.bindings.utils.BindingReflectionUtils; import org.objectstyle.wolips.bindings.wod.BindingValueKeyPath; import org.objectstyle.wolips.bindings.wod.TypeCache; import org.objectstyle.wolips.bindings.wod.WodProblem; import org.objectstyle.wolips.core.resources.types.TypeNameCollector; import org.objectstyle.wolips.eomodeler.core.model.EOModelGroup; import org.objectstyle.wolips.eomodeler.core.model.EOModelMap; import org.objectstyle.wolips.eomodeler.core.model.EOModelParserDataStructureFactory; import org.objectstyle.wolips.eomodeler.core.model.EOModelVerificationFailure; import org.objectstyle.wolips.eomodeler.core.model.PropertyListMap; import org.objectstyle.wolips.locate.LocatePlugin; import org.objectstyle.wolips.wodclipse.core.completion.WodParserCache; import org.objectstyle.wolips.wodclipse.core.refactoring.AddKeyInfo; import org.objectstyle.wolips.wodclipse.core.refactoring.AddKeyOperation; import org.objectstyle.wolips.wodclipse.core.util.EOModelGroupCache; public class WooModel { public static final String IS_DIRTY = "IS_DIRTY"; public static final String DISPLAY_GROUP_NAME = "DISPLAY_GROUP_NAME"; public static final String ENCODING = "encoding"; public static final String DEFAULT_ENCODING = ENCODING_UTF8; public static final String DEFAULT_WO_RELEASE = "WebObjects 5.0"; private IFile _file; private boolean _isDirty; private EOModelGroup _modelGroup; private String _encoding; private String _woRelease = DEFAULT_WO_RELEASE; private EOModelMap _modelMap; private PropertyListMap<Object, Object> _variables; private List<DisplayGroup> _displayGroups; private List<DisplayGroup> _removedDisplayGroups; private PropertyChangeSupport _changes = new PropertyChangeSupport(this); private PropertyChangeListener _displayGroupListener = new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { if (DisplayGroup.NAME.equals(evt.getPropertyName())) { PropertyChangeEvent newEvent = new PropertyChangeEvent(evt.getSource(), DISPLAY_GROUP_NAME, evt.getOldValue(), evt.getNewValue()); _changes.firePropertyChange(newEvent); } } }; public WooModel(final IFile file) { _file = file; try { init(); } catch (Throwable e) { throw new RuntimeException(e); } } public WooModel(final URL url) { // TODO: Fix me } public WooModel(final String contents) throws WooModelException { InputStream input = new ByteArrayInputStream(contents.getBytes()); try { loadModelFromStream(input); } catch (Throwable e) { throw new WooModelException(e.getMessage(), e); } } public WooModel(final InputStream input) throws WooModelException { try { loadModelFromStream(input); } catch (Throwable e) { throw new WooModelException(e.getMessage(), e); } } public WooModel(IEditorInput editorInput) { if (editorInput instanceof IFileEditorInput) { _file = ((IFileEditorInput) editorInput).getFile(); } try { init(); } catch (Throwable e) { throw new RuntimeException(e.getMessage(), e); } } private void init() throws IOException, PropertyListParserException { if (_file == null || !_file.exists()) { loadModelFromStream(new ByteArrayInputStream(blankContent().getBytes())); } else { loadModelFromFile(_file.getLocation().toFile()); } } public String blankContent() { // XXX Should use components default encoding charset StringBuffer sb = new StringBuffer(); sb.append("{\n"); sb.append(" \"WebObjects Release\" = \"WebObjects 5.0\";\n"); sb.append(" encoding = \"" + getEncoding() + "\";\n"); sb.append("}\n"); return sb.toString(); } private void resetModel() { _encoding = null; _woRelease = DEFAULT_WO_RELEASE; _variables = null; _modelMap = null; _displayGroups = null; _removedDisplayGroups = null; } public String getLocation() { String location; if (_file != null) { location = _file.getFullPath().toString(); } else { location = null; } return location; } public DisplayGroup[] getDisplayGroups() { if (_displayGroups != null) { return _displayGroups.toArray(new DisplayGroup[] {}); } return new DisplayGroup[0]; } public String getEncoding() { if (_encoding == null) { if (_modelMap != null && _modelMap.containsKey("encoding")) { _encoding = _modelMap.getString("encoding", true); } } if (_encoding == null) { if (_file != null && _file.exists()) { try { _encoding = _file.getParent().getDefaultCharset(); return _encoding; } catch (CoreException e) { e.printStackTrace(); } } _encoding = DEFAULT_ENCODING; } return _encoding; } public EOModelGroup getModelGroup() { if (_modelGroup == null) { _modelGroup = WodParserCache.getModelGroupCache().getModelGroup(_file.getProject()); } return _modelGroup; } public void setEncoding(String encoding) { String oldEncoding = _encoding; _encoding = encoding; _changes.firePropertyChange(ENCODING, oldEncoding, _encoding); } private void loadModelFromFile(final File file) throws IOException, PropertyListParserException { _modelMap = new EOModelMap((Map<?, ?>) WOLPropertyListSerialization.propertyListFromFile(file, new EOModelParserDataStructureFactory())); } public void loadModelFromStream(final InputStream input) throws IOException, PropertyListParserException { _modelMap = new EOModelMap((Map<?, ?>) WOLPropertyListSerialization.propertyListFromStream(input, new EOModelParserDataStructureFactory())); } @SuppressWarnings("unchecked") public void parseModel() { Set<EOModelVerificationFailure> failures = new HashSet<EOModelVerificationFailure>(); if (_modelMap == null) return; if (_modelMap.containsKey("encoding")) { _encoding = _modelMap.getString("encoding", true); } if (_modelMap.containsKey("WebObjects Release")) { _woRelease = _modelMap.getString("WebObjects Release", true); } _variables = new PropertyListMap<Object, Object>(); _displayGroups = new ArrayList<DisplayGroup>(); Map<?, ?> variables = _modelMap.getMap("variables"); if (variables != null) { EOModelMap variableMap = new EOModelMap(variables); Set<Map.Entry<String, Object>> variableEntries = variableMap.entrySet(); for (Map.Entry<String, Object> entry : variableEntries) { if (entry.getValue() instanceof Map) { EOModelMap entryMap = new EOModelMap((Map<?, ?>) entry.getValue()); String className = entryMap.getString("class", true); //XXX This should support subclasses of WODisplayGroup try { IType classType = findDisplayGroupClass(className); if (classType != null) { DisplayGroup displayGroup = new DisplayGroup(this); displayGroup.setName(entry.getKey()); displayGroup.setClassName(className); displayGroup.loadFromMap(entryMap, failures); _displayGroups.add(displayGroup); displayGroup.addPropertyChangeListener(_displayGroupListener); continue; } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } /* Fall through just in case the project classpath is broken */ if ("WODisplayGroup".equals(className) || "TBWDisplayGroup".equals(className)) { DisplayGroup displayGroup = new DisplayGroup(this); displayGroup.setName(entry.getKey()); displayGroup.setClassName(className); displayGroup.loadFromMap(entryMap, failures); _displayGroups.add(displayGroup); displayGroup.addPropertyChangeListener(_displayGroupListener); continue; } } _variables.put(entry.getKey(), entry.getValue()); } } _isDirty = false; } private IType findDisplayGroupClass(String className) throws JavaModelException { IType classType = null; TypeCache typeCache = WodParserCache.getTypeCache(); IJavaProject javaProject = JavaCore.create(_file.getProject()); String typeName = typeCache.getApiCache(javaProject).getElementTypeNamed(className); if (typeName != null) { classType = javaProject.findType(typeName); } else { TypeNameCollector typeNameCollector = new TypeNameCollector("com.webobjects.appserver.WODisplayGroup", javaProject, true); BindingReflectionUtils.findMatchingElementClassNames(className, SearchPattern.R_EXACT_MATCH, typeNameCollector, null); if (!typeNameCollector.isEmpty()) { String matchingElementClassName = typeNameCollector.firstTypeName(); classType = typeNameCollector.getTypeForClassName(matchingElementClassName); } if (classType != null) { typeCache.getApiCache(javaProject).setElementTypeForName(classType, className); } } return classType; } /** * Applies pending refactorings to the component for this WooModel. * * @param shell the shell to use for errors * @param context the runnable context to execute within */ public void refactor(Shell shell, IRunnableContext context) { try { if (_file != null) { IType componentType = LocatePlugin.getDefault().getLocalizedComponentsLocateResult(_file).getDotJavaType(); if (componentType != null) { for (DisplayGroup displayGroup : getDisplayGroups()) { String originalName = displayGroup.getOriginalName(); String newName = displayGroup.getName(); String newClassName = displayGroup.getClassName(); AddKeyInfo info = new AddKeyInfo(componentType); info.setTypeName(displayGroup.getClassName()); info.setName(newName); info.setCreateAccessorMethod(false); info.setCreateMutatorMethod(false); // Populate generic types if required IType classType = findDisplayGroupClass(displayGroup.getClassName()); String newParameterType = ""; if (classType !=null && classType.getTypeParameters().length > 0) { newParameterType = displayGroup.getEntityName(); info.setParameterTypeName(newParameterType); } IField field = componentType.getField(info.getFieldName()); if (originalName == null) { if (!field.exists()) { AddKeyOperation.addKey(info); } } else { info.setName(originalName); field = componentType.getField(info.getFieldName()); if (field.exists()) { String originalClassName = Signature.getSignatureSimpleName(Signature.getTypeErasure(field.getTypeSignature())); String types[] = Signature.getTypeArguments(field.getTypeSignature()); String originalParameterType = types.length > 0 ? Signature.getSignatureSimpleName(types[0]) : ""; if (!originalName.equals(newName) || !originalClassName.equals(displayGroup.getClassName()) || !originalParameterType.equals(newParameterType)) { info.setName(newName); AddKeyOperation.replaceField(info, originalName); }} } } if (_removedDisplayGroups != null) { for (DisplayGroup displayGroup : _removedDisplayGroups) { String originalName = displayGroup.getOriginalName(); if (originalName != null) { AddKeyInfo info = new AddKeyInfo(componentType); info.setName(originalName); IField field = componentType.getField(info.getFieldName()); if (field.exists()) { field.delete(false, null); } } } } } } } catch (Throwable e) { e.printStackTrace(); } } public void doSave() throws IOException { if (_file == null) { throw new IOException("You can not save changes to a WooModel that is not backed by a file."); } File file = _file.getLocation().toFile(); FileOutputStream writer = new FileOutputStream(file); try { doSave(writer); _isDirty = false; _removedDisplayGroups = null; _file.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor()); } catch (CoreException e) { e.printStackTrace(); } finally { writer.close(); } } public void doSave(final OutputStream writer) throws IOException { // XXX Need to validate model before saving EOModelMap modelMap = toModelMap(); try { WOLPropertyListSerialization.propertyListToStream(writer, modelMap); } catch (PropertyListParserException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public void doRevertToSaved() throws IOException, PropertyListParserException { resetModel(); loadModelFromFile(_file.getLocation().toFile()); parseModel(); } public EOModelMap toModelMap() { EOModelMap modelMap = _modelMap.cloneModelMap(); modelMap.setString("WebObjects Release", _woRelease, true); modelMap.setString("encoding", _encoding, true); EOModelMap variableMap = new EOModelMap(); if (_variables != null) { variableMap.putAll(_variables); } for (DisplayGroup displayGroup : _displayGroups) { String displayGroupName = displayGroup.getName(); EOModelMap displayGroupMap = displayGroup.toMap(); variableMap.setMap(displayGroupName, displayGroupMap, true); } modelMap.setMap("variables", variableMap, true); return modelMap; } public boolean isDirty() { return _isDirty; } public void markAsDirty() { boolean oldIsDirty = _isDirty; _isDirty = true; _changes.firePropertyChange(IS_DIRTY, oldIsDirty, _isDirty); } public void addPropertyChangeListener(final PropertyChangeListener listener) { _changes.addPropertyChangeListener(listener); } public void addPropertyChangeListener(final String name, final PropertyChangeListener listener) { _changes.addPropertyChangeListener(name, listener); } public void removePropertyChangeListener(final PropertyChangeListener listener) { _changes.removePropertyChangeListener(listener); } public void removePropertyChangeListener(final String name, final PropertyChangeListener listener) { _changes.removePropertyChangeListener(name, listener); } public void createDisplayGroup(final String name) { DisplayGroup displayGroup = new DisplayGroup(this); displayGroup.addPropertyChangeListener(_displayGroupListener); displayGroup.setName(name); _displayGroups.add(displayGroup); markAsDirty(); } public void removeDisplayGroup(final DisplayGroup selection) { selection.removePropertyChangeListener(_displayGroupListener); _displayGroups.remove(selection); if (_removedDisplayGroups == null) { _removedDisplayGroups = new LinkedList<DisplayGroup>(); } _removedDisplayGroups.add(selection); markAsDirty(); } @Override public String toString() { OutputStream modelStream = new ByteArrayOutputStream(); try { this.doSave(modelStream); } catch (Exception e) { return null; } return modelStream.toString(); } public List<WodProblem> getProblems(IJavaProject javaProject, IType type, TypeCache typeCache, EOModelGroupCache modelCache) { final List<WodProblem> problems = new ArrayList<WodProblem>(); // This was causing models to load when opening any component // even if your woo file is empty, and it doesn't APPEAR to actually // use this during this process. // EOModelGroupCache _modelCache = (EOModelGroupCache)modelCache; // EOModelGroup modelGroup = _modelCache.getModelGroup(javaProject); // if (modelGroup != null ) { // this.setModelGroup(modelGroup); // } else { // _modelCache.setModelGroup(javaProject, getModelGroup()); // } try { this.parseModel(); } catch (Throwable e) { e.printStackTrace(); problems.add(new WodProblem(e.getMessage(), null, 0, true)); return problems; } if (_file == null) { return problems; } boolean validateWooEncodings = Activator.getDefault().getPluginPreferences().getBoolean(PreferenceConstants.VALIDATE_WOO_ENCODINGS_KEY); if (validateWooEncodings) { try { String componentCharset = _file.getParent().getDefaultCharset(); String encoding = CharSetUtils.encodingNameFromObjectiveC(this.getEncoding()); if (!(encoding.equals(componentCharset))) { problems.add(new WodProblem("WOO Encoding type " + encoding + " doesn't match component " + componentCharset, null, 0, true)); } if (_file.getParent().exists()) { for (IResource element : _file.getParent().members()) { if (element.getType() == IResource.FILE) { IFile file = (IFile) element; String fileExtension = file.getFileExtension(); if (fileExtension != null && file.getFileExtension().matches("(xml|html|xhtml|wod)") && !file.getCharset().equals(encoding)) { problems.add(new WodProblem("WOO Encoding type " + encoding + " doesn't match " + file.getName() + " of " + file.getCharset(), null, 0, true)); } } } } } catch (CoreException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } if (type == null) { if (getDisplayGroups().length != 0) { problems.add(new WodProblem("Display groups are defined for component " + _file.getParent().getName() + " but class was not found", null, 0, false)); } return problems; } for (DisplayGroup displayGroup : getDisplayGroups()) { try { if (type != null) { // Validate WODisplayGroup variable is declared. BindingValueKeyPath bindingValueKeyPath = new BindingValueKeyPath(displayGroup.getName(), type, type.getJavaProject(), WodParserCache.getTypeCache()); if (!(bindingValueKeyPath.isValid() && !bindingValueKeyPath.isAmbiguous())) { //XXX Walk type hierarchy and check that is a WODisplayGroup problems.add(new WodProblem("WODisplayGroup " + displayGroup.getName() + " is configured but not declared in class", null, 0, false)); } if (!displayGroup.isHasMasterDetail()) { // Validate editing context bindingValueKeyPath = new BindingValueKeyPath(displayGroup.getEditingContext(), type, type.getJavaProject(), WodParserCache.getTypeCache()); if (!(bindingValueKeyPath.isValid() && !bindingValueKeyPath.isAmbiguous())) { problems.add(new WodProblem("Editing context for display group " + displayGroup.getName() + " not found", null, 0, false)); } } } } catch (JavaModelException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return problems; } public IProject getProject() { if (_file != null) { return _file.getProject(); } return null; } public String getName() { return _file.getName(); } public static void updateEncoding(IFile file, String charset) { WooModel model = new WooModel(file); String encoding = CharSetUtils.encodingNameFromObjectiveC(model.getEncoding()); System.out.println("WooModel.updateEncoding: Setting encoding of " + file + " from " + encoding + " to " + charset); if (!encoding.equals(charset)) { try { model._modelMap.setString("encoding", charset, true); File _file = file.getLocation().toFile(); if (!_file.exists()) { System.out.println("WooModel.updateEncoding: creating file " + _file); _file.createNewFile(); } FileOutputStream writer = new FileOutputStream(_file); WOLPropertyListSerialization.propertyListToStream(writer, model._modelMap); } catch (PropertyListParserException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (Throwable e) { e.printStackTrace(); } } } public IFile getFile() { return _file; } public void setFile(IFile file) { this._file = file; } }