All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.java.ao.Common Maven / Gradle / Ivy

Go to download

This is the full Active Objects library, if you don't know which one to use, you probably want this one.

There is a newer version: 6.1.1
Show newest version
/*
 * Copyright 2007 Daniel Spiewak
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *	    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.java.ao;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import net.java.ao.schema.FieldNameConverter;
import net.java.ao.schema.FieldNameProcessor;
import net.java.ao.schema.Ignore;
import net.java.ao.schema.PrimaryKey;
import net.java.ao.schema.info.EntityInfo;
import net.java.ao.schema.info.FieldInfo;
import net.java.ao.sql.SqlUtils;
import net.java.ao.types.TypeInfo;
import net.java.ao.types.TypeManager;
import net.java.ao.util.StringUtils;
import org.apache.commons.lang3.Validate;

import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;

import static com.google.common.collect.Lists.newArrayList;

/**
 * WARNING: Not part of the public API.  This class is public only
 * to allow its use within other packages in the ActiveObjects library.
 *
 * @author Daniel Spiewak
 */
public final class Common {

    public static , K> T createPeer(EntityManager manager, Class type, K key) throws SQLException {
        return manager.peer(manager.resolveEntityInfo(type), key);
    }

    public static String convertSimpleClassName(String name) {
        String[] array = name.split("\\.");
        return array[array.length - 1];
    }

    public static String convertDowncaseName(String name) {
        StringBuilder back = new StringBuilder();

        back.append(Character.toLowerCase(name.charAt(0)));
        back.append(name.substring(1));

        return back.toString();
    }

    /**
     * @see AO-325
     */
    @Deprecated
    public static String[] getMappingFields(FieldNameConverter converter, Class> from, Class> to) {
        Set back = new LinkedHashSet();

        for (Method method : from.getMethods()) {
            Class attributeType = getAttributeTypeFromMethod(method);

            if (attributeType == null) {
                continue;
            }

            if (to.isAssignableFrom(attributeType)) {
                back.add(converter.getName(method));
            } else if (attributeType.getAnnotation(Polymorphic.class) != null
                    && attributeType.isAssignableFrom(to)) {
                back.add(converter.getName(method));
            }
        }

        return back.toArray(new String[back.size()]);
    }

    /**
     * @see AO-325
     */
    @Deprecated
    public static String[] getPolymorphicFieldNames(FieldNameConverter converter, Class> from,
                                                    Class> to) {
        Set back = new LinkedHashSet();

        for (Method method : from.getMethods()) {
            Class attributeType = getAttributeTypeFromMethod(method);

            if (attributeType != null && attributeType.isAssignableFrom(to)
                    && attributeType.getAnnotation(Polymorphic.class) != null) {
                back.add(converter.getPolyTypeName(method));
            }
        }

        return back.toArray(new String[back.size()]);
    }

    /**
     * Note: this method leads to the creation and quick discard of
     * large numbers of {@link AnnotationDelegate} objects.  Need to
     * do some research to determine whether or not this is actually
     * a problem.
     *
     * @deprecated All annotation information should be resolved upfront using {@link net.java.ao.schema.info.EntityInfo}
     */
    @Deprecated
    public static AnnotationDelegate getAnnotationDelegate(FieldNameConverter converter, Method method) {
        return new AnnotationDelegate(method, findCounterpart(converter, method));
    }

    /**
     * Finds the corresponding method in an accessor/mutator pair based
     * on the given method (or null if no corresponding method).
     *
     * @param converter TODO
     */
    private static Method findCounterpart(FieldNameConverter converter, Method method) {
        return MethodFinder.getInstance().findCounterPartMethod(converter, method);
    }

    public static boolean isMutator(Method method) {
        return (isAnnotatedMutator(method) || isNamedAsSetter(method)) && isValidMutator(method);
    }

    public static boolean isAnnotatedMutator(Method method) {
        return method.isAnnotationPresent(Mutator.class);
    }

    private static boolean isNamedAsSetter(Method method) {
        return method.getName().startsWith("set");
    }

    private static boolean isValidMutator(Method method) {
        return method.getReturnType() == Void.TYPE && method.getParameterTypes().length == 1;
    }

    public static boolean isAccessor(Method method) {
        return (isAnnotatedAccessor(method) || isNamedAsGetter(method)) && isValidAccessor(method);
    }

    private static boolean isAnnotatedAccessor(Method method) {
        return method.isAnnotationPresent(Accessor.class);
    }

    private static boolean isNamedAsGetter(Method method) {
        return method.getName().startsWith("get")
                || method.getName().startsWith("is");
    }

    private static boolean isValidAccessor(Method method) {
        return method.getReturnType() != Void.TYPE && method.getParameterTypes().length == 0;
    }

    public static boolean isMutatorOrAccessor(Method method) {
        return isMutator(method) || isAccessor(method);
    }

    public static boolean isAnnotatedAsRelational(Method method) {
        return method.isAnnotationPresent(OneToOne.class)
                || method.isAnnotationPresent(OneToMany.class)
                || method.isAnnotationPresent(ManyToMany.class);
    }

    public static Class getAttributeTypeFromMethod(Method method) {
        if (isAnnotatedAsRelational(method)) {
            return null;
        }
        if (isMutator(method)) {
            return getMutatorParameterType(method);
        }
        if (isAccessor(method)) {
            return getAccessorReturnType(method);
        }
        return null;
    }

    private static Class getMutatorParameterType(Method method) {
        Validate.isTrue(isValidMutator(method), "Method '%s' on class '%s' is not a valid mutator", method.getName(), method.getDeclaringClass().getCanonicalName());
        return method.getParameterTypes()[0];
    }

    private static Class getAccessorReturnType(Method method) {
        Validate.isTrue(isValidAccessor(method), "Method '%s' on class '%s' is not a valid accessor", method.getName(), method.getDeclaringClass().getCanonicalName());
        return method.getReturnType();
    }

    public static String getCallingClassName(int depth) {
        StackTraceElement[] stack = new Exception().getStackTrace();
        return stack[depth + 2].getClassName();
    }

    public static List getSearchableFields(EntityManager manager, Class> type) {
        List back = new ArrayList();

        for (Method m : type.getMethods()) {
            Searchable annot = getAnnotationDelegate(manager.getNameConverters().getFieldNameConverter(), m).getAnnotation(Searchable.class);

            if (annot != null) {
                Class attributeType = Common.getAttributeTypeFromMethod(m);
                String name = manager.getNameConverters().getFieldNameConverter().getName(m);

                // don't index Entity fields
                if (name != null && !RawEntity.class.isAssignableFrom(attributeType) && !back.contains(name)) {
                    back.add(name);
                }
            }
        }

        return back;
    }

    private static Method getPrimaryKeyAccessor(Class> type) {
        final Iterable methods = MethodFinder.getInstance().findAnnotatedMethods(PrimaryKey.class, type);
        if (Iterables.isEmpty(methods)) {
            throw new RuntimeException("Entity " + type.getSimpleName() + " has no primary key field");
        }

        for (Method method : methods) {
            if (!method.getReturnType().equals(Void.TYPE) && method.getParameterTypes().length == 0) {
                return method;
            }
        }

        return null;
    }

    public static String getPrimaryKeyField(Class> type, FieldNameConverter converter) {
        final Iterable methods = MethodFinder.getInstance().findAnnotatedMethods(PrimaryKey.class, type);
        if (Iterables.isEmpty(methods)) {
            throw new RuntimeException("Entity " + type.getSimpleName() + " has no primary key field");
        }
        return converter.getName(methods.iterator().next());
    }

    public static  TypeInfo getPrimaryKeyType(TypeManager typeManager, Class> type) {
        return typeManager.getType(getPrimaryKeyClassType(type));
    }

    public static  Class getPrimaryKeyClassType(Class> type) {
        final Iterable methods = MethodFinder.getInstance().findAnnotatedMethods(PrimaryKey.class, type);
        if (Iterables.isEmpty(methods)) {
            throw new RuntimeException("Entity " + type.getSimpleName() + " has no primary key field");
        }

        final Method m = methods.iterator().next();

        Class keyType = (Class) m.getReturnType();
        if (keyType.equals(Void.TYPE)) {
            keyType = (Class) m.getParameterTypes()[0];
        }
        return keyType;
    }

    public static  K getPrimaryKeyValue(RawEntity entity) {
        if (entity instanceof EntityProxyAccessor) {
            return (K) ((EntityProxyAccessor) entity).getEntityProxy().getKey();
        }
        try {
            return (K) Common.getPrimaryKeyAccessor(entity.getEntityType()).invoke(entity);
        } catch (IllegalArgumentException e) {
            return null;
        } catch (IllegalAccessException e) {
            return null;
        } catch (InvocationTargetException e) {
            return null;
        }
    }

    public static  void validatePrimaryKey(FieldInfo primaryKeyInfo, Object value) {
        if (null == value) {
            throw new IllegalArgumentException("Cannot set primary key to NULL");
        }

        TypeInfo typeInfo = primaryKeyInfo.getTypeInfo();
        Class javaTypeClass = primaryKeyInfo.getJavaType();

        if (!typeInfo.isAllowedAsPrimaryKey()) {
            throw new ActiveObjectsException(javaTypeClass.getName() + " cannot be used as a primary key!");
        }

        typeInfo.getLogicalType().validate(value);

        if ((value instanceof String) && StringUtils.isBlank((String) value)) {
            throw new ActiveObjectsException("Cannot set primary key to blank String");
        }
    }

    public static boolean fuzzyCompare(TypeManager typeManager, Object a, Object b) {
        if (a == null && b == null) {
            return true;
        } else if (a == null || b == null) {    // implicitly, one or other is null, not both
            return false;
        }

        Object array = null;
        Object other = null;

        if (a.getClass().isArray()) {
            array = a;
            other = b;
        } else if (b.getClass().isArray()) {
            array = b;
            other = a;
        }

        if (array != null) {
            for (int i = 0; i < Array.getLength(array); i++) {
                if (fuzzyCompare(typeManager, Array.get(array, i), other)) {
                    return true;
                }
            }
        }

        return typeManager.getType(a.getClass()).getLogicalType().valueEquals(a, b)
                || typeManager.getType(b.getClass()).getLogicalType().valueEquals(b, a);
    }

    public static boolean fuzzyTypeCompare(int typeA, int typeB) {
        if (typeA == Types.BOOLEAN) {
            switch (typeB) {
                case Types.BIGINT:
                    return true;

                case Types.BIT:
                    return true;

                case Types.INTEGER:
                    return true;

                case Types.NUMERIC:
                    return true;

                case Types.SMALLINT:
                    return true;

                case Types.TINYINT:
                    return true;
            }
        }

        if ((typeA == Types.BIGINT || typeA == Types.BIT || typeA == Types.INTEGER || typeA == Types.NUMERIC
                || typeA == Types.SMALLINT || typeA == Types.TINYINT) && typeB == Types.BOOLEAN) {
            return true;
        } else if (typeA == Types.CLOB && (typeB == Types.LONGVARCHAR || typeB == Types.LONGNVARCHAR || typeB == Types.VARCHAR)) {
            return true;
        } else if ((typeA == Types.LONGVARCHAR || typeA == Types.VARCHAR) && typeB == Types.CLOB) {
            return true;
        } else if ((typeA == Types.BIGINT || typeA == Types.BIT || typeA == Types.DECIMAL || typeA == Types.DOUBLE
                || typeA == Types.FLOAT || typeA == Types.INTEGER || typeA == Types.REAL || typeA == Types.SMALLINT
                || typeA == Types.TINYINT) && typeB == Types.NUMERIC) {
            return true;
        } else if (typeA == Types.VARCHAR && typeB == Types.NVARCHAR) {
            return true;
        }

        return typeA == typeB;
    }

    /**
     * Gets all the methods of an entity that correspond to a value field. This means fields that are stored as values
     * in the database as opposed to fields (IDs) that define a relationship to another table in the database.
     * Note that the values are retrieved based on the relationship annotations, at the field level,
     * which the user may not have entered.
     *
     * @param entity    the entity to look up the methods from
     * @param converter the field name converter currently in use for entities
     * @return the set of method found
     */
    public static Set getValueFieldsMethods(final Class> entity, final FieldNameConverter converter) {
        return Sets.filter(Sets.newHashSet(entity.getMethods()), new Predicate() {
            public boolean apply(Method m) {
                final AnnotationDelegate annotations = getAnnotationDelegate(converter, m);
                return !annotations.isAnnotationPresent(Ignore.class)
                        && !annotations.isAnnotationPresent(OneToOne.class)
                        && !annotations.isAnnotationPresent(OneToMany.class)
                        && !annotations.isAnnotationPresent(ManyToMany.class);
            }
        });
    }

    /**
     * Gets all the names of fields of an entity that correspond to a value field. This means fields that are stored as
     * values in the database as opposed to fields (IDs) that define a relationship to another table in the database.
     *
     * @param entityInfo the entity to look up the methods from
     * @return the set of names found
     */
    public static ImmutableSet getValueFieldsNames(final EntityInfo, ?> entityInfo, final FieldNameConverter converter) {
        List valueFieldsNames = new ArrayList();

        for (FieldInfo fieldInfo : entityInfo.getFields()) {
            // filter out just the value fields - we need to remove any entities from polymorphic relationships
            if (!Entity.class.isAssignableFrom(fieldInfo.getJavaType())) {
                // apply the name converter if we have a getter
                if (fieldInfo.hasAccessor()) {
                    valueFieldsNames.add(converter.getName(fieldInfo.getAccessor()));
                }
            }
        }

        return ImmutableSet.copyOf(valueFieldsNames);
    }

    public static List preloadValue(Preload preload, final FieldNameConverter fnc) {
        final List value = newArrayList(preload.value());
        if (fnc instanceof FieldNameProcessor) {
            return Lists.transform(value, new Function() {
                @Override
                public String apply(String from) {
                    return ((FieldNameProcessor) fnc).convertName(from);
                }
            });
        } else {
            return value;
        }
    }

    public static String where(OneToOne oneToOne, final FieldNameConverter fnc) {
        return where(oneToOne.where(), fnc);
    }

    public static String where(OneToMany oneToMany, final FieldNameConverter fnc) {
        return where(oneToMany.where(), fnc);
    }

    public static String where(ManyToMany manyToMany, final FieldNameConverter fnc) {
        return where(manyToMany.where(), fnc);
    }

    private static String where(String where, final FieldNameConverter fnc) {
        if (fnc instanceof FieldNameProcessor) {
            final Matcher matcher = SqlUtils.WHERE_CLAUSE.matcher(where);
            final StringBuffer sb = new StringBuffer();
            while (matcher.find()) {
                matcher.appendReplacement(sb, convert(fnc, matcher.group(1)));
            }
            matcher.appendTail(sb);
            return sb.toString();
        } else {
            return where;
        }
    }

    public static String convert(FieldNameConverter fnc, String column) {
        if (fnc instanceof FieldNameProcessor) {
            return ((FieldNameProcessor) fnc).convertName(column);
        } else {
            return column;
        }
    }

    /**
     * Closes the {@link java.sql.ResultSet} in a {@code null} safe manner and quietly, i.e without throwing nor logging
     * any exception
     *
     * @param resultSet the result set to close
     */
    @Deprecated
    public static void closeQuietly(ResultSet resultSet) {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                // ignored
            }
        }
    }

    /**
     * Closes the {@link java.sql.Statement} in a {@code null} safe manner and quietly, i.e without throwing nor logging
     * any exception
     *
     * @param statement the statement to close
     */
    @Deprecated
    public static void closeQuietly(Statement statement) {
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                // ignored
            }
        }
    }

    /**
     * Closes the {@link java.sql.Connection} in a {@code null} safe manner and quietly, i.e without throwing nor logging
     * any exception
     *
     * @param connection the connection to close, can be {@code null}
     */
    @Deprecated
    public static void closeQuietly(Connection connection) {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                // ignored
            }
        }
    }

    /**
     * Shortens a string to the given length if necessary. The process of shortening is stable.
     * 

* Strings shorter than length will not be shortened. * For longer strings the first length - length/3 - 1 characters remain unchanged. * The remaining space is filled with string's last length/3 hashCode characters. *

* * @param string to be shortened. * @param length to which string will be shortened. * @return a shortened string. */ public static String shorten(String string, int length) { if (string == null || string.length() <= length) { return string; } final int tailLength = length / 3; final int prefixEndPosition = length - tailLength - 1; final int hash = Math.abs((int) (string.hashCode() % Math.round(Math.pow(10, tailLength)))); return string.substring(0, prefixEndPosition) + hash; } /** * Extracts a part of the string that remains unchanged during shortening process. *

* For strings shorter than specified length the string itself is returned. * For longer strings the first length - length/3 - 1 characters are returned. *

* * @param string for which the prefix will be extracted. * @param length used in {@link #shorten}ing process. * @return part of the string that remains unchanged during shortening process. * @see #shorten(String, int) */ public static String prefix(String string, int length) { final int tailLength = length / 3; final int prefixEndPosition = length - tailLength - 1; if (string == null || prefixEndPosition > string.length()) { return string; } return string.substring(0, prefixEndPosition); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy