/*-------------------------------------------------------------------------------
Copyright (c)2000 TogetherSoft Corporation. Patents pending. All rights reserved.
-------------------------------------------------------------------------------*/

package com.togethersoft.modules.patterns.UNIVERSAL;

import java.util.Enumeration;
import java.util.Vector;
import java.text.MessageFormat;
import java.beans.PropertyEditor;

import com.togethersoft.openapi.sci.*;

import com.togethersoft.openapi.sci.enum.SciInheritanceEnumeration;

import com.togethersoft.openapi.sci.pattern.SciPatternProperty;
import com.togethersoft.openapi.sci.pattern.SciDefaultPattern;
import com.togethersoft.openapi.sci.pattern.SciPatternManager;
import com.togethersoft.openapi.sci.pattern.SciPatternUtil;
import com.togethersoft.openapi.sci.pattern.SciPattern;
import com.togethersoft.openapi.sci.pattern.SciPatternCategory;

import com.togethersoft.openapi.util.propertyMap.PropertyMapListener;
import com.togethersoft.openapi.util.propertyMap.PropertyMapEvent;
import com.togethersoft.openapi.util.propertyMap.PropertyMap;
import com.togethersoft.openapi.util.propertyMap.Property;
import com.togethersoft.openapi.util.propertyMap.DefaultPropertyMap;

import com.togethersoft.openapi.ide.message.IdeMessageType;
import com.togethersoft.openapi.ide.message.IdeMessageManagerAccess;

import com.togethersoft.openapi.ide.inspector.IdeInspector;
import com.togethersoft.openapi.ide.inspector.IdeInspectorComponent;
import com.togethersoft.openapi.ide.inspector.IdeInspectorProperty;
import com.togethersoft.openapi.ide.inspector.IdeInspectorPropertySetComponent;

import com.togethersoft.openapi.ide.inspector.util.property.PropertyMapInspectorProperty;

/**
 * Class <code>PatternBase</code> is an auxiliary base class for patterns. This
 * class contains methods and stores constants used in the patterns.
 * Methods <code>prepare()</code> and <code>apply()</code> should be implemented
 * by each inhereted class.
 *
 * @author TogetherSoft
 */
abstract public class PatternBase {
    public static final String PROPERTY                 = "Property";
    public static final String ATTRIBUTE                = "Attribute";
    // Common Inspector field labels
    public static final String EXPORT_DOCUMENTATION     = "Copy documentation";
    public static final String SELECTED_CLASS_ROLE      = "Use selected class as";
    public static final String CREATE_PATTERN_LINKS     = "Create pattern links";
    public static final String INITIALIZATION_VARIANT   = "Initialization variant";
    // Prefixes
    public static final String PREFIX_GET               = "get";
    public static final String PREFIX_SET               = "set";
    public static final String PREFIX_IS                = "is";
    public static final String PREFIX_MY                = "my";
    public static final String PREFIX_LINK              = "lnk";
    public static final String PREFIX_VISIT             = "visit";
    public static final String PREFIX_CREATE            = "create";
    //
    public static final String CONSTRUCTOR_PARAMETER    = "Constructor parameter";
    public static final String SET_METHOD               = "Set method";
    public static final String HARDCODED                = "Hardcoded";
    public static final String NONE                     = "<none>";
    //
    public static final String CLASS                    = "class";
    public static final String INTERFACE                = "interface";
    public static final String NEGATION                 = "!";
    public static final String QUOTE                    = "\"";
    public static final String EMPTY_STRING             = "";
    // Common error messages
    public static final String CODE_GENERATION_ERROR =  "Code generation error.";
    public static final String CANNOT_CREATE_PATTERN_UI = "Cannot create pattern UI.";
    // Primitive types
    public static final String VOID =                   "void";
    public static final String BOOLEAN =                "boolean";
    public static final String BOOL =                   "bool";
    public static final String LONG =                   "long";
    public static final String INT =                    "int";
    public static final String SHORT =                  "short";
    public static final String FLOAT =                  "float";
    public static final String DOUBLE =                 "double";
    public static final String BYTE =                   "byte";
    public static final String CHAR =                   "char";
    public static final String WCHAR =                  "wchar";
    public static final String STRING =                 "String";
    // Existing stereotypes:
    //"type", "implementationClass", "stereotype", "Idl-Interface",
    //"struct", "union", "enumeration", "exception", "Boundary", "Entity"
    public static final String STEREOTYPE =             "stereotype";
    public static final String STYPE_ROLE =             "role";
    public static final String STYPE_CONTAINER =        "container";
    public static final String STYPE_MOMENT_INTERVAL =  "moment-interval";
    public static final String STYPE_MI_DETAIL =        "mi-detail";
    public static final String STYPE_PARTY =            "party";
    public static final String STYPE_PLACE =            "place";
    public static final String STYPE_THING =            "thing";
    public static final String STYPE_VISITOR =          "visitor";
    public static final String STYPE_PLUG_IN_POINT =    "plug-in point";
    public static final String STYPE_DESCRIPTION =      "description";
    public static final String STYPE_FACTORY =          "factory";
    public static final String STYPE_ACTOR =            "actor";
    public static final String STYPE_METACLASS =        "metaclass";
    public static final String STYPE_ABSTRACTFACTORY =  "abstractfactory";
    public static final String STYPE_UTILITY =          "utility";
    public static final String STYPE_INTERFACE =        "interface";
    public static final String STYPE_POWERTYPE =        "powertype";
    public static final String STYPE_PROCESS =          "process";
    public static final String STYPE_THREAD =           "thread";
    // Link properties
    public static final String CARDINALITY_SUPPLIER =   "supplierCardinality";
    public static final String CARDINALITY_CLIENT =     "clientCardinality";
    public static final String CARD_ZERO_ONE =          "0..1";
    public static final String CARD_ZERO_MANY =         "0..*";
    public static final String CARD_ONE =               "1";
    public static final String CARD_MANY =              "*";
    public static final String CARD_ONE_MANY =          "1..*";
    // Roles
    public static final String ROLE_CLIENT =            "clientRole";
    public static final String ROLE_SUPPLIER =          "supplierRole";
    public static final String ROLE_PLAN =              "plan";
    public static final String ROLE_ACTUAL =            "actual";
    // Java standard class types
    public static final String ENUMERATION =            "Enumeration";
    public static final String ENUMERATION_FULL_NAME =  "java.util.Enumeration";
    public static final String VECTOR =                 "Vector";
    public static final String VECTOR_FULL_NAME =       "java.util.Vector";
    // Access modifiers
    public static final String PROTECTED =              "protected";
    public static final String PRIVATE =                "private";
    public static final String PUBLIC =                 "public";
    public static final String PACK_LOCAL =             "package local";
    // Template pattern names
    public static final String DEFAULT_ASSOCIATION =    "Default Association";
    public static final String DEFAULT_AGGREGATION =    "Default Aggregation";

/* ********************************************** */
   /**
    * Default implementation of the method defined in <code>SciPattern</code>.
    */
    protected boolean prepare() {
        for (int i = 0; i < myAttributePropertyVector.size(); i++) {
            Attribute attr = (Attribute)myAttributePropertyVector.elementAt(i);
            attr.myChanged = false;
            setPropertyValue(attr.myClassPropertyName, EMPTY_STRING);
        }
        return true;
    }

   /**
    * Default implementation of the method defined in <code>SciPattern</code>.
    */
    protected void apply() {
        myAllClassesAreNew = true;
        if (getPatternManager() == null) {
            IdeMessageManagerAccess.printMessage(IdeMessageType.ERROR, "Pattern manager not found.");
            return;
        }
        if (SciLanguage.JAVA.equals(getLanguage()) && getInterfacePattern() == null) {
            IdeMessageManagerAccess.printMessage(IdeMessageType.ERROR, "No default interface pattern.");
            return;
        }
        if (getClassPattern() == null) {
            IdeMessageManagerAccess.printMessage(IdeMessageType.ERROR, "No default class pattern.");
            return;
        }
    }

/* ********************************************** */

    protected void finalize() throws Throwable {
        super.finalize();

        if (myAttributePropertyVector != null) {
            myAttributePropertyVector.removeAllElements();
            myAttributePropertyVector = null;
        }

        if (myListenedPropertiesVector != null) {
            myListenedPropertiesVector.removeAllElements();
            myListenedPropertiesVector = null;
        }
        myPropertyMapListener = null;
        myInspector = null;
    }

   /**
    * Defines name of the selected pattern that will be displayed in the
    * second section of the chooser window header. This name is retrieved by the
    * predefined key <code>SciPatternProperty.PATTERN_DISPLAY_NAME</code> that
    * is stored in the property map of each pattern.
    *
    * @param patternName <code>String</code> that appears in the title string of the window
    */
    protected void setPatternDisplayName(String patternName) {
        setPropertyValue(SciPatternProperty.PATTERN_DISPLAY_NAME, patternName);
    }

    /**
     * Sets the name of the current inspector page that will be displayed in the
     * third section of the chooser window header. This name is retrieved by the
     * predefined key <code>SciPatternProperty.TITLE</code> that is stored in
     * the property map of each pattern.
     *
     * @param currentPagetitle <code>String</code> name of the inspector page
     *						  that appears in the title string of the window
     */
    protected void setCurrentPageTitle(String currentPageTitle) {
        setPropertyValue(SciPatternProperty.TITLE, currentPageTitle);
    }
    /**
     * Returns the title of the current inspector page.
     *
     * @return <code>String</code> name of the inspector page
     *        that appears in the title string of the window
     *
     */
    protected String getCurrentPageTitle() {
        return getStringPropertyValue(SciPatternProperty.TITLE);
    }

    /**
     * Inserts new operation to the specified <code>sciClass</code> and makes stub
     * implementation of this operation.
     *
     * @param sciClass <code>SciClass</code> class where this operation is inserted
     * @param operationName	<code>String</code> name of the operation added to this class
     * @return	<code>SciOperation</code> object that corresponds to this name
     */
    public SciOperation pasteNewOperation(SciClass sciClass, String operationName) {
        return pasteNewOperation(sciClass, operationName, null);
    }

    /**
     * Inserts new operation to the specified <code>sciClass</code> and makes stub
     * implementation of this operation,  with the specified return type.
     *
     * @param sciClass <code>SciClass</code> class where this operation is inserted
     * @param operationName	<code>String</code> name of the operation added to this class
     * @param returnType <code>String</code> return type of this operation
     * @return	<code>SciOperation</code> object that corresponds to this operation name
     */
    public SciOperation pasteNewOperation(SciClass sciClass, String operationName, String returnType) {
        SciOperation operation = getFactory().newOperation();
        operation.setName(operationName);
        if (returnType != null) {
            operation.getReturnType().setText(returnType);
        }
        SciPatternUtil.makeStubImplementation(operation);
        operation = (SciOperation)sciClass.paste(operation, null, false);
        return operation;
    }

    /**
     * Inserts new attribute with the given name to the specified <code>sciClass</code>.
     *
     * @param sciClass <code>SciClass</code> where this attribute is inserted
     * @param attributeName <code>String</code> name of the attribute to be added to this class
     * @return <code>SciAttribute</code> object that represents the created attribute,
     *		   or <code>null</code>.
    */
    public SciAttribute pasteNewAttribute(SciClass sciClass, String attributeName) {
        SciAttribute attribute = getFactory().newAttribute();
        attribute.setName(attributeName);
        attribute = (SciAttribute)sciClass.paste(attribute, null, false);
        return attribute;
    }
    /**
     * Auxiliary method that allows to create a message string according to the
     * textual <code>pattern</code>, filled with the specified values from the
     * array passed as a parameter.
     *
     * @param pattern <code>String</code> text template that contains message format  (integers from 0 to 9 in {})
     * @param arguments <code>Object[]</code> array of string type objects to be substituted instead of pattern numbers
     * @return <code>String</code> with filled positions
     * @see Attribute#message patterns
     */
    public String createTextByPattern(String pattern, Object[] arguments) {
        /* Can be changed to "SciPatternUtil.replaceMacro()" */
        return MessageFormat.format(pattern, arguments);
    }

/* Methods for accessing to property map. */

    /**
     * @return <code>PropertyMap</code> object
     */
    public PropertyMap getProperties() {
        return myProperties;
    }

    /**
     * Adds specified listener for the property map.
     *
     * @param listener <code>PropertyMapListener</code> object to be added
     * @param name <code>String</code>
     */
    public void addPropertyMapListener(String name, PropertyMapListener listener) {
        getProperties().addPropertyMapListener(name, listener);
    }

    /**
     * Deletes specified listener from the property map.
     *
     * @param listener <code>PropertyMapListener</code> object to be added
     * @param name <code>String</code>
     */
    public void removePropertyMapListener(String name, PropertyMapListener listener) {
        getProperties().removePropertyMapListener(name, listener);
    }

    /**
     * Gets the value of the property with the specified key.
     *
     * @param name <code>String</code> key of the property to be retreived
     * @return <code>String</code> value of this property
     */
    public final String getStringPropertyValue(String name) {
        return (String)getProperties().getPropertyValue(name);
    }

    /**
     * Gets the value of the boolean property with the specified key.
     *
     * @param name <code>String</code> key of the property to be retreived
     * @return <code>boolean</code> value of this property
     */
    public final boolean getBooleanPropertyValue(String name) {
        return ((Boolean)getProperties().getPropertyValue(name)).booleanValue();
    }
    /**
     * Returns an enumeration of properties that have same key.
     *
     * @param name <code>String</code> key for which all available properties are sought
     * @return <code>Enumeration</code> of properties for this key
     */
    public final Enumeration propertiesEnumeration(String name) {
        return getProperties().properties(name);
    }

// returns number of properties with the same key
    /**
     * Returns the number of properties that have same key.
     *
     * @param name <code>String</code> key for which all available properties are counted
     * @return <code>int</code> number of properties that have this key
     */
    public final int getNumProperties(String name) {
        int num = 0;
        Enumeration properties = propertiesEnumeration(name);
        while (properties.hasMoreElements()) {
            properties.nextElement();
            num++;
        }
        return num;
    }

    /**
     * Returns the property value for the specified key.
     *
     * @param name <code>String</code> key for which the property value is retreived
     * @return <code>Object</code> that represents the value of this property
     */
    public Object getPropertyValue(String name) {
        return getProperties().getPropertyValue(name);
    }

    /**
     * Sets specified property value for the specified key.
     *
     * @param name <code>String</code> key for which the property value is set
     * @param value <code>Object</code> value of this property to be set
     */
    public void setPropertyValue(String name, Object value) {
        getProperties().setPropertyValue(name, value);
    }

    /**
     * Adds specified property value with the specified key.
     *
     * @param name <code>String</code> key
     * @param value <code>Object</code> value of this property to be added
     */
    public void addPropertyValue(String name, Object value) {
        getProperties().addProperty(name, value);
    }
    /**
     * Enables or disables specified control <code>propertyName</code>,  depending
     * on the specified flag. Of <code>enable</code> flag is <code>false</code>,
     * this control is shown grey and may not be handled.
     *
     * @param propertyName <code>String</code> name of the control
     * @param enable  <code>boolean</code> flag. If  <code>true</code>, this control
     *				  is enabled
     */
    public void enableItem(String propertyName, boolean enable) {
        if (propertyName == null || propertyName.length() == 0) {
            return;
        }
        PropertyMapInspectorProperty property = (PropertyMapInspectorProperty) findIdeInspectorProperty(propertyName);
        if (property != null) {
            property.setWritable(enable);
        }
    }
    /**
     * Searches the Ide Inspector for the property with the specified key. Returns
     * this property or <code>null</code>,  if such property does not exist.
     *
     * @param propertyName <code>String</code> key of the property to be found
     * @return <code>IdeInspectorProperty</code> that corresponds to this key, or
     *		   <code>null</code> if such property does not exist, or key is not defined
     */
    public IdeInspectorProperty findIdeInspectorProperty(String propertyName) {
        if (propertyName == null || propertyName.length() == 0) {
            return null;
        }
        IdeInspectorComponent [] pages = getIdeInspector().getComponents();
        for (int n=0; n < pages.length; n++) {
            IdeInspectorProperty [] properties = ((IdeInspectorPropertySetComponent) pages[n]).getProperties();
            for (int i=0; i < properties.length; i++) {
                if (propertyName.equals(properties[i].getName())) {
                    return properties[i];
                }
            }
        }
        return null;
    }

    /**
     * Looks for a page of IdeInspector by its specified internal name.
     *
     * @param pageName <code>String</code> name that describes this component of the inspector
     * @return <code>IdeInspectorComponent</code> that corresponds to this name
     */
    public IdeInspectorComponent findIdeInspectorPageByName(String pageName) {
        if(pageName == null || pageName.length() == 0) {
            return null;
        }
        IdeInspectorComponent [] pages = getIdeInspector().getComponents();
        for (int n=0; n < pages.length; n++) {
            if (pageName.equals(pages[n].getName())) {
                return pages[n];
            }
        }
        return null;
    }

    /**
     * Allows to assign specified editor to the specified property.
     *
     * @param propertyKey <code>String</code> key of the control to which the editor is assigned
     * @param editor      <code>PropertyEditor</code> jbject for this control
     * @return <code>true</code> if property editor is successfully assigned,
     *		   <code>false</code> if the control for this key does not exist
     */
    public boolean setInspectorPropertyEditor(String propertyKey, PropertyEditor editor) {
        IdeInspectorProperty inspectorProperty = findIdeInspectorProperty(propertyKey);
        if (inspectorProperty == null) {
            return false;
        }
        inspectorProperty.setPropertyEditor(editor);
        return true;
    }

/* ********************************************** */

    /**
     * Requests the status of the property EXPORT_DOCUMENTATION.
     *
     * @return <code>boolean</code> status of this property
     */
    protected boolean exportDocumentation() {
        return getBooleanPropertyValue(EXPORT_DOCUMENTATION);
    }
    /**
     * Requests the status of the property CREATE_PATTERN_LINKS.
     *
     * @return <code>boolean</code> status of this property
     */
    protected boolean createPatternLinks() {
        return getBooleanPropertyValue(CREATE_PATTERN_LINKS);
    }

/* Methods for getting standard objects. */

    /**
     * Gets the default language of the current project.
     *
     * @return <code>String</code> that describes the default programming language
     */
    protected String getLanguage() {
        return getStringPropertyValue(SciPatternProperty.PATTERN_LANGUAGE);
    }

    /**
     * Gets the object that contains auxiliary methods for the default language.
     *
     * @return <code>SciLanguageHelper</code> object for the current default language
     */
    protected SciLanguageHelper getLanguageHelper() {
        return getModel().getLanguageHelper(getLanguage());
    }

    /**
     * Gets the container where the results of the pattern work will be placed.
     * Container for a class pattern is a package, container for the members is a class.
     *
     * @return <code>SciContainer</code> object
     */
    protected SciContainer getContainer() {
        return (SciContainer)getProperties().getPropertyValue(SciPatternProperty.CONTAINER);
    }
    /**
     * Gets an element for which this pattern is applied.
     *
     * @return <code>SciElement</code> object that represents the selected element,
     *		   to which this pattern is applied,  or <code>null</code> if this element
     *		   is created on empty background of a diagram
     */
    protected SciElement getElement() {
        return (SciElement)getProperties().getPropertyValue(SciPatternProperty.ELEMENT);
    }

    protected SciModel getModel() {
        return SciModelAccess.getModel();
    }

    protected SciFactory getFactory() {
        return getModel().getFactory(getLanguage());
    }

    protected SciGenericFactory getGenericFactory() {
        return getModel().getGenericFactory(getLanguage());
    }

    protected SciPatternManager getPatternManager() {
        return getModel().getPatternManager();
    }

    protected SciPattern getClassPattern() {
        return getPatternManager().getDefaultClassPattern(getContainer(), getLanguage(), SciDefaultPattern.CLASS);
    }

    protected SciPattern getInterfacePattern() {
        return getPatternManager().getDefaultClassPattern(getContainer(), getLanguage(), SciDefaultPattern.INTERFACE);
    }

/* Check for standard errors. */
    /**
     * Makes sure that the class name syntax is correct. Gets class name for the
     * specified property key. Class name can include package names delimited by dots.
     * If class name is null, of the key is wrong, throws exception and issues a
     * pre-defined error message.
     *
     * @param propertyName <code>String</code> key of the property
     * @return <code>String</code> validated class name
     * @exception	PatternBaseException if class name is null or the key is wrong
     */
    protected String classNameShouldBeCorrect(String propertyName) throws PatternBaseException {
        String result = getStringPropertyValue(propertyName);
        if (result == null || result.length() == 0) {
            Object[] arguments = { propertyName };
            String message = createTextByPattern(PATTERN_SHOULD_BE_DEFINED, arguments);
            throw new PatternBaseException(message);
        }
        return checkStringValue(result, true);
    }
    /**
     * Makes sure that the identifier name syntax is correct. Gets id name for the
     * specified property key. If the name is null, of the key is wrong, throws
     * exception and issues a pre-defined error message.
     *
     * @param propertyName <code>String</code> key of the property
     * @return <code>String</code> validated id name
     * @exception	PatternBaseException if id name is null or the key is wrong
     */
    protected String identifierShouldBeCorrect(String propertyName) throws PatternBaseException {
        String result = getStringPropertyValue(propertyName);
        if (result == null || result.length() == 0) {
            Object[] arguments = { propertyName };
            String message = createTextByPattern(PATTERN_SHOULD_BE_DEFINED, arguments);
            throw new PatternBaseException(message);
        }
        return checkStringValue(result, false);
    }

    /**
     * Makes sure that the class name syntax is correct. Gets class name for the
     * specified property from the property map. Class name can include package names delimited by dots.
     * If class name is null, of the key is wrong, throws exception and issues a
     * pre-defined error message.
     *
     * @param property <code>Property</code> object for which the class name is validated
     * @return <code>String</code> validated class name
     * @exception	PatternBaseException if class name is null or the key is wrong
     */
    protected String classNameShouldBeCorrect(Property property) throws PatternBaseException {
        String result = (String)property.getValue();
        if (result == null || result.length() == 0) {
            return result;
        }
        return checkStringValue(result, true);
    }

    /**
     * Checks if this <code>aClass</code> is an interface. This method is required
     * for certain patterns that are not applicable to the interfaces.
     *
     * @param aClass <code>SciClass</code> object to be checked if it is an interface
     * @exception PatternBaseException
     */
    protected void cannotApplyToInterface(SciClass aClass) throws PatternBaseException {
        if (aClass != null && aClass.hasProperty(SciProperty.INTERFACE)) {
            throw new PatternBaseException("Cannot apply this pattern to interface.");
        }
    }

    /**
     * Checks if a certain property of the specified pattern is a class. Interfaces
     * are not allowed.
     *
     * @param propertyName <code>String</code> key of the property to be checked
     * @param aClass <code>SciClass</code> object to be checked
     * @exception PatternBaseException
     */
    protected void cannotBeAnInterface(String propertyName, SciClass aClass) throws PatternBaseException {
        if (aClass != null && aClass.hasProperty(SciProperty.INTERFACE)) {
            Object[] arguments = { propertyName, aClass.getName() };
            String message = createTextByPattern(PATTERN_CANNOT_BE_INTERFACE, arguments);
            throw new PatternBaseException(message);
        }
    }

    /**
     * Checks if the specified element is read-only. If a created class needs to be saved,
     * the appropriate file should not have an attribute "read-only". Otherwise
     * throws an exception and shows a pre-defined error message.
     *
     * @param propertyName <code>String</code> key of the property
     * @param element <code>SciElement</code> to be checked
     * @exception PatternBaseException
     */
    protected void shouldBeWritable(String propertyName, SciElement element) throws PatternBaseException {
        if (element != null && element.isReadOnly()) {
            Object[] arguments = { propertyName, element.getName() };
            String message = createTextByPattern(PATTERN_SHOULD_BE_WRITABLE, arguments);
            throw new PatternBaseException(message);
        }
    }

    /**
     * Checks the current language for the pattern that is applicable for Java only.
     * If the language is other than Java, throws an exception and shows a pre-defined error message.
     *
     * @exception PatternBaseException
     */
    protected void implementedForJavaOnly() throws PatternBaseException {
        if (!SciLanguage.JAVA.equals(getLanguage())) {
            Object[] arguments = { getLanguageHelper().getPresentableLanguageName() };
            String message = createTextByPattern(PATTERN_NOT_IMPLEMENTED_FOR, arguments);
            throw new PatternBaseException(message);
        }
    }

    /**
     * Checks the current language for the pattern that is not applicable for IDL.
     * If the language is IDL, throws an exception and shows a pre-defined error message.
     *
     * @exception PatternBaseException
     */
    protected void notImplementedForIDL() throws PatternBaseException {
        if (SciLanguage.IDL.equals(getLanguage()) || SciLanguage.COMIDL.equals(getLanguage())) {
            Object[] arguments = { getLanguageHelper().getPresentableLanguageName() };
            String message = createTextByPattern(PATTERN_NOT_IMPLEMENTED_FOR, arguments);
            throw new PatternBaseException(message);
        }
    }

    /**
     * Checks if an attribute with the specified name and type is present in the specified class.
     * Displays a text that says that such element should be present, if it is missing.
     *
     * @param attributeName <code>String</code> name of the attribute who's presence is checked
     * @param containingClass <code>SciClass</code> container of this attribute
     * @param type <code>SciClass</code> type of this attribute
     * @exception PatternBaseException
     */
    protected void checkAttributeName(String attributeName, SciClass containingClass, SciClass type) throws PatternBaseException {
        if (containingClass == null) {
            return;
        }
        SciAttribute attribute = SciUtil.findAttributeByName(containingClass, attributeName);
        if (attribute != null && !type.equals(attribute.getType().getReferencedElement())) {
            Object[] arguments = { attributeName, type.getName() };
            String message = createTextByPattern(PATTERN_SHOULD_BE_INSTANCE, arguments);
            throw new PatternBaseException(message);
        }
    }

    /**
     * Checks if an attribute with the specified name and type is present in the specified class,
     * the type being identified by its name. <br>
     * Displays a text that says that such element should be present, if it is missing.
     *
     * @param attributeName <code>String</code> name of the attribute who's presence is checked
     * @param containingClass <code>SciClass</code> container of this attribute
     * @param typeName <code>String</code> name of the type of this attribute
     * @exception PatternBaseException
     */
    protected void checkAttributeName(String attributeName, SciClass containingClass, String typeName) throws PatternBaseException {
        if (containingClass == null) {
            return;
        }
        SciAttribute attribute = SciUtil.findAttributeByName(containingClass, attributeName);
        if (attribute == null || typeName.equals(attribute.getType().getText())) {
            return;
        }
        if (SciLanguage.JAVA.equals(getLanguage())) {
            String shortTypeName = typeName.substring(typeName.lastIndexOf(".") + 1);
            if (shortTypeName.equals(attribute.getType().getText())) {
                return;
            }
        }

        Object[] arguments = { attributeName, typeName };
        String message = createTextByPattern(PATTERN_SHOULD_BE_INSTANCE, arguments);
        throw new PatternBaseException(message);
    }

    /**
     * Checks if the returned type of the specified <code>operation</code> corresponds
     * to the specified return type. If these two types do not match, throws an exception
     * and shows an error message.
     *
     * @param operation <code>SciOperation</code> who's type is being checked
     * @param typeName <code>String</code> name of the return type
     * @exception PatternBaseException
     */
    protected void checkReturnType(SciOperation operation, String typeName) throws PatternBaseException {
        if (operation == null || typeName.equals(operation.getReturnType().getText())) {
            return;
        }
        if (SciLanguage.JAVA.equals(getLanguage())) {
            String shortTypeName = typeName.substring(typeName.lastIndexOf(".") + 1);
            if (shortTypeName.equals(operation.getReturnType().getText())) {
                return;
            }
        }

        Object[] arguments = { operation.getName(), typeName };
        String message = createTextByPattern(PATTERN_ALREADY_EXISTS, arguments);
        throw new PatternBaseException(message);
    }

    /**
     * Checkes if the specified class extends the base class, identified by the
     * class name passed as a parameter.
     *
     * @param aClass <code>SciClass</code> object to be checked
     * @param baseClassName <code>String</code> name of the base class from which
     *		  this class should be inherited
     * @exception PatternBaseException
     */
    protected void shouldExtend(SciClass aClass, String baseClassName) throws PatternBaseException {
        if (SciLanguage.JAVA.equals(getLanguage()) && aClass != null) {
            SciInheritanceEnumeration extendsEnum = aClass.getExtendsList().inheritances();
            if (extendsEnum.hasMoreElements() && !extendsEnum.nextSciInheritance().getText().equals(baseClassName)) {
                Object[] arguments = { aClass.getName(), baseClassName };
                String message = createTextByPattern(PATTERN_CANNOT_EXTEND, arguments);
                throw new PatternBaseException(message);
            }
        }
    }

    /**
     * Checkes if the specified class extends the specified <code>baseClass</code>.
     *
     * @param aClass <code>SciClass</code> object to be checked
     * @param baseClass <code>SciClass</code> object that represents the base class from which
     *		  this class should be inherited
     * @exception PatternBaseException
     */
    protected void shouldExtend(SciClass aClass, SciClass baseClass) throws PatternBaseException {
        if (aClass != null && baseClass != null && !SciPatternUtil.canCreateInheritance(aClass, baseClass)) {
            Object[] arguments = { aClass.getName(), baseClass.getName() };
            String message = createTextByPattern(PATTERN_CANNOT_EXTEND, arguments);
            throw new PatternBaseException(message);
        }
    }

    /**
     * Makes sure that the specified element implements specified base class,
     * and checks if it is an interface. If the flag passed as a parameter is
     * <code>false</code>, then this element cannot implement an interface. Should
     * this be the case, throws an exception and shows an error message.
     *
     * @param baseClass 			<code>SciClass</code> object that should be implemented by this element
     * @param concreteElementName 	<code>String</code> name of the element to be checked
     * @param allowInterfaces 		<code>boolean</code> flag that defines if this element can
     *						  		implement interfaces
     * @exception PatternBaseException
     */
    protected void checkConcreteElements(SciClass baseClass, String concreteElementsName, boolean allowInterfaces) throws PatternBaseException {
        Enumeration elements = propertiesEnumeration(concreteElementsName);
        while (elements.hasMoreElements()) {
            String concreteElementName = classNameShouldBeCorrect((Property)elements.nextElement());
            if (concreteElementName == null || concreteElementName.length() == 0) {
                continue;
            }
            SciClass concreteElementClass = findClassByName(concreteElementName);
            shouldBeWritable(concreteElementsName, concreteElementClass);
            if (!allowInterfaces) {
                cannotBeAnInterface(concreteElementsName, concreteElementClass);
            }
            if (baseClass != null && !baseClass.hasProperty(SciProperty.INTERFACE)) {
                shouldExtend(concreteElementClass, baseClass);
            }
        }
    }

/* ********************************************** */

    protected String getLanguageSpecificNullStatement() {
        return SciLanguage.CPP.equals(getLanguage()) ? "0" : "null";
    }

    protected String getLanguageSpecificBooleanStatement() {
        return SciLanguage.CPP.equals(getLanguage()) ? BOOL : BOOLEAN;
    }

    protected String getLanguageSpecificAccessorStatement() {
        return SciLanguage.CPP.equals(getLanguage()) ? "->" : ".";
    }

/* ********************************************** */

    /**
     * Looks for a class identified by teh given <code>name</code>.
     * Returns this class, or <code>null</code> if this class is not found.
     *
     * @param name <code>String</code> name of the class to be found
     * @return <code>SciClass</code> object for this name, or <code>null</code> if this class not found
     */
    protected SciClass findClassByName(String name) {
        SciClass classByName = getModel().findClass(getLanguage(), name);
        if (classByName == null) {
            classByName = SciUtil.findClassByName(getContainer(), name);
        }
        return classByName;
    }

    /**
     * Looks for a class identified by the given <code>name</code> and
     * returns this class, or a newly created class if this class is not found.
     *
     * @param name <code>String</code> name of the class to be found
     * @param isInterface <code>boolean</code> flag that defines if this class is an interface
     * @return <code>SciClass</code> object for this name
     */
    protected SciClass getClassByName(String name, boolean isInterface) {
        SciClass classByName = findClassByName(name);
        SciContainer container = getContainer();
        if (classByName == null) {
            String newClassName = EMPTY_STRING;
            if (SciLanguage.JAVA.equals(getLanguage())) {
                int lastIndex = name.lastIndexOf(".");
                if (lastIndex >= 0) {
                    String packageName = name.substring(0, lastIndex);
                    name = name.substring(lastIndex + 1);
                    container = getModel().findPackage(packageName);
                    if (container == null) {
                        container = getModel().findClass(getLanguage(), packageName);
                    }
                    if (container == null) {
                        return null;
                    }
                }
            }

            if (isInterface && (SciLanguage.JAVA.equals(getLanguage()))) {
                classByName = getPatternManager().createClassByPattern(getInterfacePattern(), container);
            }
            else {
                classByName = getPatternManager().createClassByPattern(getClassPattern(), container);
            }
            classByName.setName(name);
            if (!name.equals(classByName.getName())) {
                Object[] arguments = { name, classByName.getName() };
                String message = createTextByPattern(PATTERN_COULDNT_SET_NAME, arguments);
                IdeMessageManagerAccess.printMessage(IdeMessageType.ERROR, message);
            }
        }
        else {
            myAllClassesAreNew = false;
        }

        return classByName;
    }

    /**
     * Validates the given <code>value</code> from the viewpoint of the current language.
     *
     * @param value 		<code>String</code> to be checked
     * @param isClassName 	<code>boolean</code> flag that defines if this value is a class name.
     *		  				if <code>true</code>, the name can contain dots
     * @returns <code>String</code> validated name
     * @exception PatternBaseException
     */
    protected String checkStringValue(String value, boolean isClassName) throws PatternBaseException {
        String identifier = value;
        if (isClassName && SciLanguage.JAVA.equals(getLanguage())) {
            int lastIndex = identifier.lastIndexOf('.');
            if (lastIndex >= 0) {
                String packageName = identifier.substring(0, lastIndex);
                if (EMPTY_STRING.equals(packageName) || (getModel().findClass(getLanguage(), packageName) == null &&
                    getModel().findPackage(packageName) == null)) {
                    Object[] arguments = { packageName };
                    String message = createTextByPattern(PATTERN_CONTAINER_NOT_FOUND, arguments);
                    throw new PatternBaseException(message);
                }
                identifier = identifier.substring(lastIndex + 1);
            }
        }

        if (!getLanguageHelper().isValidIdentifier(identifier)) {
            Object[] arguments = { identifier };
            String message = createTextByPattern(PATTERN_INVALID_IDENTIFIER, arguments);
            throw new PatternBaseException(message);
        }

        return value;
    }

    /**
     * Requests a pattern by it's <code>category</code> and name from the pattern manager.
     * Pattern can belong to such categories as <code>SciPattern.LINK, SciPattern.MEMBER,</code>
     * or <code>SciPattern.CLASS</code>.
     *
     * @param category <code>String</code> that defines the scope where this pattern is applicable
     * @param patternName <code>String</code> name of the pattern to be retreived
     * @return <code>SciPattern</code> object that belongs to this category and has the given name
     */
    protected SciPattern findPattern(String category, String patternName) {
        return getPatternManager().findPattern(getLanguage(), category, patternName);
    }

    /**
     * Creates a link using the specified pattern,  container and destination classes.
     * The pattern is selected among the default patterns,  if appropriate flag
     * passed as a parameter is set to <code>true</code>.
     *
     * @param patternName	<code>String</code> name if the pattern used to create this link
     * @param container		<code>SciClass</code> container object for this link
     * @param destination	<code>SciClass</code> target object of this link
     * @param defaultPattern <code>boolean</code> that specifies if this pattern is selected among the default patterns
     */
    protected SciElement createLinkByPattern(String patternName, SciClass container, SciClass destinationClass, String attributeName, boolean defaultPattern) {
        if (patternName == null || patternName.length() == 0)  return null;
        if (/*container == null ||*/ destinationClass == null)  return null;
        if (attributeName == null || attributeName.length() == 0)  return null;

        SciPattern pattern = null;
        if (defaultPattern) {
            pattern = getPatternManager().getDefaultLinkPattern(container, destinationClass, patternName);
        }
        else {
            pattern = findPattern(SciPatternCategory.LINK, patternName);
        }
        SciElement element = (SciElement) getPatternManager().createLinkByPattern(pattern, container, destinationClass);
        if (element == null) {
            return null;
        }
        element.setName(attributeName);
        return element;
    }

    /**
     * Creates a link using the specified pattern,  container and destination classes,
     * and sets specified attribute name.
     *
     * @param patternName	<code>String</code> name if the pattern used to create this link
     * @param container		<code>SciClass</code> container object for this link
     * @param destination	<code>SciClass</code> target object of this link
     * @param attributeName <code>String</code> name assigned to this link
     */
    protected SciElement createLinkByDefaultPattern(String patternName, SciClass container, SciClass destinationClass, String attributeName) {
        SciPattern pattern = getPatternManager().getDefaultLinkPattern(container, destinationClass, patternName);
        if (pattern == null) {
            return null;
        }
        SciElement element = (SciElement) getPatternManager().createLinkByPattern(pattern, container, destinationClass);
        if (element == null) {
            return null;
        }
        element.setName(attributeName);
        return element;
    }

    protected String ConvertToString(String[] values) {
        String result = EMPTY_STRING;
        for (int i = 0; i < values.length; i++) {
            if (i > 0) {
                result += ", ";
            }
            result += QUOTE + values[i] + QUOTE;
        }
        return result;
    }

    protected String ConvertToString(Vector values) {
        String result = EMPTY_STRING;
        for (int i = 0; i < values.size(); i++) {
            if (i > 0) {
                result += ", ";
            }
            result += QUOTE + (String)values.elementAt(i) + QUOTE;
        }
        return result;
    }
    /**
	 * Checks if the specified class has an interface tag.<br>
     * Java interfaces are displayed in the diagrams with italic font,  unlike
     * classes that are displayed with the plain font.
     * This method is required for c++ classes, to conform with the way of displaying
     * interfaces in the dialgrams.
     *
     * @param aClass <code>SciClass</code> object to be checked
     * @return <code>true</code> if the tags are available for this class
     */
    protected boolean hasInterfaceTag( SciClass aClass ) {
        SciTagList list = aClass.getTagList();
        return (list == null) ? false : (list.getTag(INTERFACE) != null);
    }

/* Methods for accessing to and using the IdeInspector. */

    /**
     * Gets the reference to <code>IdeInspector</code> with which this pattern works.
     *
     * @return <code>IdeInspector</code> object for this pattern
     */
    public final IdeInspector getIdeInspector() {
        return myInspector;
    }

    protected PatternUIBuilder createUIBuilder(boolean clear) {
        return new PatternUIBuilder(this, clear);
    }

    public void assignListenedInspectorProperties(Vector propertiesVector) {
        if (propertiesVector == null) {
            return;
        }

        removeAllListenedInspectorProperties();
        if (propertiesVector.size() == 0) {
            return;
        }

        myListenedPropertiesVector = null;
        myListenedPropertiesVector = propertiesVector;

        myPropertyMapListener = new PropertyMapListener() {
            public void propertiesChanged(PropertyMapEvent event) {
                if (myListenedPropertiesVector == null || myListenedPropertiesVector.size() == 0) {
                    return;
                }

                for (int i=0; i < myListenedPropertiesVector.size(); i++) {
                    Object property = event.getPropertyKey();
                    if (((String)myListenedPropertiesVector.elementAt(i)).equals((String)property)) {
                        if (propertyChangedAction(property)) {
                            break;
                        }
                    }
                }
            }
        };

        // assign listeners to specified properties
        for (int i=0; i < myListenedPropertiesVector.size(); i++) {
            addPropertyMapListener((String)myListenedPropertiesVector.elementAt(i), myPropertyMapListener);
        }
    }

    public void removeAllListenedInspectorProperties() {
        if (myPropertyMapListener != null) {
            removePropertyMapListener(null, myPropertyMapListener); // remove ALL property listeners
            myPropertyMapListener = null;
        }
        if (myListenedPropertiesVector != null) {
            myListenedPropertiesVector.removeAllElements();
            myListenedPropertiesVector = null;
        }
    }

    /**
     * Provides an action to be invoked when a property of the <code>IdeInspector</code> updates.
     * A user can override this method and define own action.
     *
     * @param propertyKey <code>String</code> key of the property for which the action is invoked
     * @return <code>true</code>
     */
    protected boolean propertyChangedAction(Object propertyKey) {
        // Default action - update components
        getIdeInspector().update();
        return true;
    }

    private static class Attribute {
        public Attribute(String classPropertyName, String attributePropertyName) {
            myClassPropertyName = classPropertyName;
            myAttributePropertyName = attributePropertyName;
        }

        boolean myChanged = false;
        String myClassPropertyName;
        String myAttributePropertyName;
    }

    protected boolean myAllClassesAreNew;

    private PropertyMap   myProperties = new DefaultPropertyMap();
    private Vector        myAttributePropertyVector = new Vector();

    private IdeInspector  myInspector = new IdeInspector(null);;

    private PropertyMapListener myPropertyMapListener = null;
    private Vector              myListenedPropertiesVector = null;

    // Message patterns
    private static final String PATTERN_SHOULD_BE_DEFINED       = "{0} should be defined.";
    private static final String PATTERN_CANNOT_BE_INTERFACE     = "{0} ({1}) cannot be an interface.";
    private static final String PATTERN_SHOULD_BE_WRITABLE      = "{0} ({1}) should be writable.";
    private static final String PATTERN_SHOULD_BE_INSTANCE      = "{0} should be instance of {1}.";
    private static final String PATTERN_ALREADY_EXISTS          = "{0} already exists and return type is not {1}.";
    private static final String PATTERN_CANNOT_EXTEND           = "Class {0} cannot extend class {1}.";
    private static final String PATTERN_COULDNT_SET_NAME        = "Couldn't set class name {0}, set {1} instead.";
    private static final String PATTERN_CONTAINER_NOT_FOUND     = "Container \"{0}\" not found.";
    private static final String PATTERN_INVALID_IDENTIFIER      = "\"{0}\" is invalid identifier.";
    private static final String PATTERN_NOT_IMPLEMENTED_FOR     = "This pattern is not implemented for {0}.";
}
