Utils.java

/* $Id$
 *****************************************************************************
 * Copyright (c) 2009 Contributors - see below
 * 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:
 *    drahmann
 *****************************************************************************
 *
 * Some portions of this file was previously release using the BSD License:
 */

// Copyright (c) 2007 The Regents of the University of California. All
// Rights Reserved. Permission to use, copy, modify, and distribute this
// software and its documentation without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph appear in all copies. This software program and
// documentation are copyrighted by The Regents of the University of
// California. The software program and documentation are supplied "AS
// IS", without any accompanying services from The Regents. The Regents
// does not warrant that the operation of the program will be
// uninterrupted or error-free. The end-user understands that the program
// was developed for research purposes and is advised not to rely
// exclusively on the program for any reason. IN NO EVENT SHALL THE
// UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
// SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
// UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

package org.argouml.language.sql;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;

import org.argouml.model.Facade;
import org.argouml.model.Model;

final class Utils {
    /**
     * Return an association named <code>assocName</code> that is connected to
     * relation.
     * 
     * @param relation
     *            The relation where to search the association.
     * @param assocName
     *            The name of the association to search.
     * @return The association if found, <code>null</code> else.
     */
    public static Object getAssociationForName(Object relation, String assocName) {
        Object association = null;

        Collection assocEnds = Model.getFacade().getAssociationEnds(relation);
        for (Iterator it = assocEnds.iterator(); it.hasNext();) {
            Object assocEnd = it.next();
            Object assoc = Model.getFacade().getAssociation(assocEnd);
            String name = Model.getFacade().getName(assoc);
            if (name.equals(assocName)) {
                association = assoc;
            }
        }

        return association;
    }

    /**
     * Search the attribute named <code>attributeName</code> in the given
     * relation. If there exist more than one attribute the first one is
     * returned.
     * 
     * @param relation
     *            The relation in which to search the attribute.
     * @param attributeName
     *            The name of the attribute to search.
     * @return The attribute if found, <code>null</code> else.
     */
    public static Object getAttributeForName(Object relation,
            String attributeName) {
        Object attribute = null;

        Collection attributes = Model.getFacade().getAttributes(relation);
        for (Iterator it = attributes.iterator(); it.hasNext();) {
            Object attr = it.next();
            if (Model.getFacade().getName(attr).equals(attributeName)) {
                attribute = attr;
                break;
            }
        }

        return attribute;
    }

    /**
     * Build a list of all foreign key attributes that refer a specific
     * association.
     * 
     * @param relation
     *            The relation which contains the fk-attributes.
     * @param association
     *            The association for which to return the fk-attributes.
     * @return A list of all attributes. If there is no attribute, an empty list
     *         is returned.
     */
    public static List getFkAttributes(Object relation, Object association) {
        String assocName = Model.getFacade().getName(association);

        Collection attributes = Model.getFacade().getAttributes(relation);
        Iterator it = attributes.iterator();
        List fkAttributes = new ArrayList();
        while (it.hasNext()) {
            Object attribute = it.next();
            String s = Model.getFacade().getTaggedValueValue(attribute,
                    GeneratorSql.ASSOCIATION_NAME_TAGGED_VALUE);

            if (s.equals(assocName)) {
                fkAttributes.add(attribute);
            }
        }

        return fkAttributes;
    }

    /**
     * Build a list of all primary key attributes of entity.
     * 
     * @param relation
     *            The relation for which to return the pk-attributes.
     * @return A list of all primary key attributes. If there is no
     *         pk-attribute, the list is empty.
     */
    public static List getPrimaryKeyAttributes(Object relation) {
        List result = new ArrayList();

        Collection attributes = Model.getFacade().getAttributes(relation);

        for (Iterator it = attributes.iterator(); it.hasNext();) {
            Object attribute = it.next();
            if (isPk(attribute)) {
                result.add(attribute);
            }
        }

        return result;
    }

    /**
     * Returns if an attribute is a foreign key attribute. Effectively checks if
     * the attribute is of stereotype
     * {@link GeneratorSql#FOREIGN_KEY_STEREOTYPE}.
     * 
     * @param attribute
     *            The attribute to check.
     * @return <code>true</code> if it is a fk-attribute, <code>false</code>
     *         else.
     * @see Facade#isStereotype(Object, String)
     */
    public static boolean isFk(Object attribute) {
        return Model.getFacade().isStereotype(attribute,
                GeneratorSql.FOREIGN_KEY_STEREOTYPE);
    }

    /**
     * Returns if an attribute is a primary key attribute. Effectively checks if
     * the attribute is of stereotype
     * {@link GeneratorSql#PRIMARY_KEY_STEREOTYPE}.
     * 
     * @param attribute
     *            The attribute to check.
     * @return <code>true</code> if it is a pk-attribute, <code>false</code>
     *         else.
     * @see Facade#isStereotype(Object, String)
     */
    public static boolean isPk(Object attribute) {
        return Model.getFacade().isStereotype(attribute,
                GeneratorSql.PRIMARY_KEY_STEREOTYPE);
    }

    /**
     * Returns if an attribute is an attribute that should be auto incremented.
     * Effectively checks if the attribute is of stereotype
     * {@link GeneratorSql#AUTOINC_KEY_STEREOTYPE}.
     *
     * @param attribute
     *            The attribute to check.
     * @return <code>true</code> if it is a auto increment attribute,
     *         <code>false</code> else.
     * @see Facade#isStereotype(Object, String)
     */
    public static boolean isAutoIncrement(Object attribute) {
            return Model.getFacade().isStereotype(attribute,
                            GeneratorSql.AUTOINC_KEY_STEREOTYPE);
    }

    /**
     * Returns if an attribute is not nullable. Effectively checks if the
     * attribute is of stereotype {@link GeneratorSql#NOT_NULL_STEREOTYPE}.
     * 
     * @param attribute
     *            The attribute to check.
     * @return <code>true</code> if it is not nullable, <code>false</code>
     *         else.
     * @see Facade#isStereotype(Object, String)
     */
    public static boolean isNotNull(Object attribute) {
        return Model.getFacade().isStereotype(attribute,
                GeneratorSql.NOT_NULL_STEREOTYPE);
    }

    /**
     * Returns if an attribute is nullable. Effectively checks if the attribute
     * is of stereotype {@link GeneratorSql#NULL_STEREOTYPE}.
     * 
     * @param attribute
     *            The attribute to check.
     * @return <code>true</code> if it is nullable, <code>false</code> else.
     * @see Facade#isStereotype(Object, String)
     */
    public static boolean isNull(Object attribute) {
        return Model.getFacade().isStereotype(attribute,
                GeneratorSql.NULL_STEREOTYPE);
    }

    /**
     * Get the attribute a foreign key attribute is referencing to. Returns
     * <code>null</code> if the source attribute cannot be determined.
     * 
     * @param fkAttribute
     *            The foreign key attribute.
     * @param srcRelation
     *            The entity the foreign key is referencing to.
     * @return The referenced attribute.
     */
    public static Object getSourceAttribute(Object fkAttribute,
            Object srcRelation) {
        String srcColName = Model.getFacade().getTaggedValueValue(fkAttribute,
                GeneratorSql.SOURCE_COLUMN_TAGGED_VALUE);
        Object srcAttr = null;
        if (srcColName.equals("")) {
            srcColName = Model.getFacade().getName(fkAttribute);
            srcAttr = Utils.getAttributeForName(srcRelation, srcColName);
            if (srcAttr == null) {
                Collection pkAttrs = Utils.getPrimaryKeyAttributes(srcRelation);
                if (pkAttrs.size() == 1) {
                    srcAttr = pkAttrs.iterator().next();
                }
            }
        } else {
            srcAttr = Utils.getAttributeForName(srcRelation, srcColName);
        }
        return srcAttr;
    }

    /**
     * Takes a list of strings and joins them with <code>separators</code>.
     * 
     * @param strings
     *            The list of strings to be joined.
     * @param separators
     *            The string that should be put between the separate strings.
     * @return The joined string.
     */
    public static String stringsToString(List strings, String separators) {
        StringBuffer sb = new StringBuffer();
        Iterator it = strings.iterator();
        while (it.hasNext()) {
            String s = (String) it.next();
            if (sb.length() > 0) {
                sb.append(separators);
            }
            sb.append(s);
        }
        return sb.toString();
    }

    /**
     * Takes a list of strings and joins them with a comma.
     * 
     * @param strings
     *            The list of strings.
     * @return The joined string.
     */
    public static String stringsToCommaString(List strings) {
        return stringsToString(strings, ",");
    }

    /**
     * Private constructor so no instance can be created.
     */
    private Utils() {

    }

    /**
     * Separates a <code>String</code> using the given delimiters and puts the
     * resulting tokens to a <code>List</code>.
     * 
     * @param string
     *            The string to separate.
     * @param delimiters
     *            The delimiters that should used to separate the string.
     * 
     * @return A <code>List</code> containing all string tokens.
     */
    public static List stringToStringList(String string, String delimiters) {
        StringTokenizer st = new StringTokenizer(string, delimiters);
        List result = new ArrayList();
        while (st.hasMoreTokens()) {
            result.add(st.nextToken());
        }
        return result;
    }

    /**
     * Separates a <code>String</code> using the standard
     * <code>StringTokenizer</code> delimiters and puts the resulting tokens
     * to a <code>List</code>.
     * 
     * @param string
     *            The string to separate.
     * 
     * @return A <code>List</code> containing all string tokens.
     */
    public static List stringToStringList(String string) {
        StringTokenizer st = new StringTokenizer(string);
        List result = new ArrayList();
        while (st.hasMoreTokens()) {
            result.add(st.nextToken());
        }
        return result;
    }

    /**
     * Get a short table name that can be used in triggers etc. Shortens the
     * name by first removing all vowels from the left to the right. If that's
     * not enough the resulting string is just truncated.
     * 
     * @param longName
     * @param maxLength
     * @return
     */
    public static String getShortName(String longName, int maxLength) {
        char[] vowels = new char[] { 'a', 'e', 'i', 'o', 'u' };
        String shortName = longName;
        while (shortName.length() > maxLength) {
            int index = -1;
            for (int i = 0; i < vowels.length; i++) {
                int lastIndex = shortName.lastIndexOf(vowels[i]);
                if (lastIndex > index) {
                    index = lastIndex;
                }
            }
            
            if (index == -1) {
                break;
            }
            
            String firstPart = shortName.substring(0, index);
            String lastPart = shortName.substring(index + 1, shortName.length());
            shortName = firstPart + lastPart;
        }
        if (shortName.length() > maxLength) {
            shortName = shortName.substring(0, maxLength);
        }
        return shortName;    
    }
    
    /**
     * The prefix in URL:s that are files.
     */
    private static final String FILE_PREFIX = "file:";

    /**
     * The prefix in URL:s that are jars.
     */
    private static final String JAR_PREFIX = "jar:";

    /**
     * Class file suffix.
     */
    public static final String CLASS_SUFFIX = ".class";

    public static String getModuleRoot() {
        // Use a little trick to find out where this module is being loaded
        // from. (Code was "stolen" from ModuleLoader2 and modified)
        String resName = Utils.class.getName();
        resName = "/" + resName.replace('.', '/') + CLASS_SUFFIX;
        String extForm = Utils.class.getResource(resName).toExternalForm();

        String moduleRoot = extForm.substring(0, extForm.length()
                - resName.length());

        // If it's a jar, clean it up and make it look like a file url
        if (moduleRoot.startsWith(JAR_PREFIX)) {
            moduleRoot = moduleRoot.substring(JAR_PREFIX.length());
            if (moduleRoot.endsWith("!")) {
                moduleRoot = moduleRoot.substring(0, moduleRoot.length() - 1);
            }
            int p = moduleRoot.lastIndexOf('/');
            moduleRoot = moduleRoot.substring(0, p);
        }

        if (moduleRoot.startsWith(FILE_PREFIX)) {
            moduleRoot = moduleRoot.substring(FILE_PREFIX.length());
        }

        return moduleRoot;
    }
}