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) || "ERXDisplayGroup".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;
}
}