/*
* Chrysalix
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
* See the AUTHORS.txt file in the distribution for a full listing of
* individual contributors.
*
* Chrysalix is free software. Unless otherwise indicated, all code in Chrysalix
* is licensed to you under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* Chrysalix is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.modeshape.modeler.eclipse;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.modeshape.modeler.Model;
import org.modeshape.modeler.ModelObject;
import org.modeshape.modeler.ModelerException;
import org.modeshape.modeler.ui.FocusTreeModel;
import org.chrysalix.common.Logger;
import org.chrysalix.common.Logger.Level;
import org.chrysalix.common.ObjectUtil;
import org.chrysalix.common.TextUtil;
/**
* A {@link FocusTree} content provider for {@link Model models}.
*/
public final class FocusTreeModeShapeModelerXsdModel extends FocusTreeModel {
private static final boolean DEBUG = false;
private static Logger _logger;
private static final String JAVA_PKG = "java.lang.";
/**
* No arg methods from {@link ModelObject} that will be added as properties
*/
private static final String[] MODEL_OBJECT_METHODS =
new String[] { "absolutePath",
"index",
"mixinTypes",
"modelRelativePath",
"name",
"primaryType"
};
/**
* No arg methods from {@link Model} that will be added as properties
*/
private static final String[] MODEL_METHODS =
new String[] { "allDependenciesExist",
"dependencies",
"externalLocation",
"missingDependencies",
"metamodel"
};
/**
* Obtains an element count suffix.
*
* @param item
* the item whose collection type suffix is being requested (cannot be <code>null</code>)
* @param value
* the item value (can be <code>null</code>)
* @return the suffix or empty string
*/
public static String collectionTypeSuffix( final Object item,
final Object value ) {
if ( item.getClass().isArray() ) {
if ( value != null ) return " (" + ( ( Object[] ) item ).length + ")";
} else if ( Collection.class.isAssignableFrom( item.getClass() ) ) {
if ( value != null ) return " (" + ( ( Collection< ? > ) item ).size() + ")";
}
return TextUtil.EMPTY_TEXT;
}
private static void debug( final String message,
final Object... args ) {
if ( DEBUG ) {
if ( _logger == null ) {
_logger = Logger.getLogger( FocusTreeModeShapeModelerXsdModel.class );
_logger.setLevel( Level.DEBUG );
}
_logger.debug( message, args );
}
}
/**
* @param typeToFormat
* the type being formatted (cannot be <code>null</code>)
* @return the formatted type (never <code>null</code>)
*/
public static String formatType( final String typeToFormat ) {
String type = typeToFormat;
// see if an array
if ( type.startsWith( "[" ) ) {
String temp = type.substring( 1 );
int count = 1;
while ( temp.startsWith( "[" ) ) {
++count;
temp = temp.substring( count );
}
// strip off single letter representing JNI type
type = temp.substring( 1 );
// array class name will have a semicolon at the end so strip it off
if ( type.endsWith( ";" ) ) {
type = type.substring( 0, ( type.length() - 1 ) );
}
// add array brackets at the end
for ( int i = 0; i < count; ++i ) {
type += '[';
}
for ( int i = 0; i < count; ++i ) {
type += ']';
}
}
if ( type.indexOf( JAVA_PKG ) == 0 ) {
return type.substring( JAVA_PKG.length() );
}
return type;
}
private Collection< PropertyModel > addPropertiesFromMethods( final ModelObject modelObj,
final String[] methodNames ) throws Exception {
final Collection< PropertyModel > result = new ArrayList< PropertyModel >( methodNames.length );
final Class< ? extends ModelObject > clazz = modelObj.getClass();
for ( final String methodName : methodNames ) {
final Method method = clazz.getMethod( methodName );
final PropertyModel propModel = new PropertyModel( modelObj,
method.getName(),
method.getReturnType(),
method.invoke( modelObj ) );
result.add( propModel );
}
return result;
}
/**
* @param childName
* the name of the child (cannot be <code>null</code> or empty)
* @param parent
* the parent of the child being requested (cannot be <code>null</code>)
* @return the child or <code>null</code> if not found
* @throws ModelerException
* if any error occurs
*/
public Object child( final String childName,
final Object parent
) throws ModelerException {
for ( final Object obj : children( parent ) ) {
final Object name = name( obj );
if ( childName.equals( name ) ) {
return obj;
}
}
return null;
}
/**
* {@inheritDoc}
*
* @see FocusTreeModel#childCount(java.lang.Object)
*/
@Override
public int childCount( final Object item ) throws ModelerException {
return children( item ).length;
}
/**
* {@inheritDoc}
*
* @see FocusTreeModel#children(java.lang.Object)
*/
@Override
public Object[] children( final Object item ) throws ModelerException {
if ( item instanceof ModelObject ) {
try {
return childrenOf( ( ModelObject ) item );
} catch ( final Exception e ) {
throw new ModelerException( e );
}
}
if ( item instanceof ModelContent ) {
try {
return childrenOf( ( ModelContent ) item );
} catch ( final Exception e ) {
throw new ModelerException( e );
}
}
return super.children( item );
}
private Object[] childrenOf( final ModelContent content ) throws Exception {
return content.children();
}
private Object[] childrenOf( final ModelObject modelObj ) throws Exception {
final List< Object > kids = new ArrayList<>();
final ModelObject[] temp = modelObj.children();
if ( temp.length != 0 ) {
kids.addAll( Arrays.asList( temp ) );
}
// add methods from ModelObject that are treated as properties
kids.addAll( addPropertiesFromMethods( modelObj, MODEL_OBJECT_METHODS ) );
// add methods from Model that are treated as properties
if ( modelObj instanceof Model ) {
kids.addAll( addPropertiesFromMethods( modelObj, MODEL_METHODS ) );
}
// add properties as children
for ( final String prop : modelObj.propertyNames() ) {
final Object value = modelObj.value( prop );
kids.add( new PropertyModel( modelObj, prop, value.getClass(), value ) );
}
return kids.toArray();
}
/**
* {@inheritDoc}
*
* @see FocusTreeModel#hasChildren(java.lang.Object)
*/
@Override
public boolean hasChildren( final Object item ) throws ModelerException {
if ( item instanceof ModelObject ) {
try {
debug( "%s has children: %s", ( ( ModelObject ) item ).name(), ( ( ModelObject ) item ).hasChildren() );
return ( children( item ).length != 0 );
} catch ( final Exception e ) {
throw new ModelerException( e );
}
}
if ( item instanceof ModelContent ) return ( ( ModelContent ) item ).hasChildren();
return super.hasChildren( item );
}
/**
* {@inheritDoc}
*
* @see FocusTreeModel#hasName(java.lang.Object)
*/
@Override
public boolean hasName( final Object item ) throws ModelerException {
if ( item instanceof ModelObject ) {
try {
return !TextUtil.isEmpty( ( ( ModelObject ) item ).name() );
} catch ( final ModelerException e ) {
throw new ModelerException( e );
}
}
if ( item instanceof ModelContent ) return !TextUtil.isEmpty( ( ( ModelContent ) item ).name() );
return super.hasName( item );
}
/**
* {@inheritDoc}
*
* @see FocusTreeModel#hasType(java.lang.Object)
*/
@Override
public boolean hasType( final Object item ) {
if ( item instanceof ModelObject ) return true;
if ( item instanceof ModelContent ) return !TextUtil.isEmpty( ( ( ModelContent ) item ).type() );
return super.hasType( item );
}
/**
* {@inheritDoc}
*
* @see FocusTreeModel#hasValue(java.lang.Object)
*/
@Override
public boolean hasValue( final Object item ) throws ModelerException {
return ( value( item ) != null );
}
/**
* {@inheritDoc}
*
* @see FocusTreeModel#name(java.lang.Object)
*/
@Override
public Object name( final Object item ) throws ModelerException {
if ( item instanceof ModelObject ) {
try {
return ( ( ModelObject ) item ).name();
} catch ( final Exception e ) {
throw new ModelerException( e );
}
}
if ( item instanceof ModelContent ) return ( ( ModelContent ) item ).name();
return super.name( item );
}
/**
* {@inheritDoc}
*
* @see FocusTreeModel#qualifiedName(java.lang.Object)
*/
@Override
public Object qualifiedName( final Object item ) throws ModelerException {
if ( item instanceof ModelObject ) {
return ( ( ModelObject ) item ).absolutePath();
}
if ( item instanceof ModelContent ) {
return ( ( ModelContent ) item ).qualifiedName();
}
return super.qualifiedName( item );
}
/**
* {@inheritDoc}
*
* @see FocusTreeModel#type(java.lang.Object)
*/
@Override
public Object type( final Object item ) throws ModelerException {
if ( item instanceof ModelObject ) {
try {
return formatType( ( ( ModelObject ) item ).primaryType() );
} catch ( final Exception e ) {
throw new ModelerException( e );
}
}
if ( item instanceof ModelContent ) return formatType( ( ( ModelContent ) item ).type() );
return super.type( item );
}
/**
* {@inheritDoc}
*
* @see FocusTreeModel#value(java.lang.Object)
*/
@Override
public Object value( final Object item ) throws ModelerException {
if ( item instanceof ModelObject ) return null;
if ( item instanceof ModelContent ) return ( ( ModelContent ) item ).value();
return super.value( item );
}
interface ModelContent {
Object[] children() throws Exception;
boolean hasChildren();
String name();
String qualifiedName();
String type();
Object value();
}
class PropertyModel implements ModelContent {
private final ModelObject model;
private final String name;
private final Class< ? > type;
private final Object value;
PropertyModel( final ModelObject model,
final String propName,
final Class< ? > returnType,
final Object propValue ) {
this.model = model;
this.name = propName;
this.type = returnType;
this.value = propValue;
}
@Override
public Object[] children() throws Exception {
if ( isPrimitive() ) return ObjectUtil.EMPTY_ARRAY;
if ( this.model.propertyHasMultipleValues( this.name ) ) {
final Object[] values = this.model.values( this.name );
final Collection< ValueModel > result = new ArrayList<>( values.length );
for ( final Object value : values ) {
result.add( new ValueModel( this, value.getClass(), value ) );
}
return result.toArray();
}
if ( isCollection() && ( this.value != null ) ) {
final Collection< ? > values = ( ( Collection< ? > ) this.value );
final Collection< ValueModel > result = new ArrayList<>( values.size() );
for ( final Object value : values ) {
result.add( new ValueModel( this, value.getClass(), value ) );
}
return result.toArray();
}
if ( this.type.isArray() && ( this.value != null ) ) {
final Object[] values = ( Object[] ) this.value;
final Collection< ValueModel > result = new ArrayList<>( values.length );
for ( final Object value : values ) {
result.add( new ValueModel( this, value.getClass(), value ) );
}
return result.toArray();
}
// must be a complex/custom type
return ( ( this.value == null ) ? ObjectUtil.EMPTY_ARRAY
: new Object[] { new ValueModel( this, value.getClass(), this.value ) } );
}
@Override
public boolean hasChildren() {
return !isPrimitive();
}
private boolean isCollection() {
return Collection.class.isAssignableFrom( this.type );
}
private boolean isPrimitive() {
if ( this.type.equals( Boolean.class )
|| this.type.equals( String.class )
|| this.type.equals( Byte.class )
|| this.type.equals( Character.class )
|| this.type.equals( Short.class )
|| this.type.equals( Integer.class )
|| this.type.equals( Long.class )
|| this.type.equals( Float.class )
|| this.type.equals( Double.class ) ) {
return true;
}
return this.type.isPrimitive();
}
@Override
public String name() {
return this.name;
}
@Override
public String qualifiedName() {
return ( this.model.absolutePath() + '/' + this.name );
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return this.name;
}
@Override
public String type() {
return ( formatType( this.type.getName() ) + collectionTypeSuffix( this.type, value() ) );
}
@Override
public Object value() {
return ( isPrimitive() ? this.value : null );
}
}
class ValueModel implements ModelContent {
private final PropertyModel parent;
private final Class< ? > type;
private final Object value;
ValueModel( final PropertyModel propertyModel,
final Class< ? > metamodel,
final Object modelValue ) {
this.parent = propertyModel;
this.type = metamodel;
this.value = modelValue;
}
@Override
public Object[] children() {
return ObjectUtil.EMPTY_ARRAY;
}
@Override
public boolean hasChildren() {
return false;
}
@Override
public String name() {
return null;
}
PropertyModel parent() {
return this.parent;
}
@Override
public String qualifiedName() {
return null;
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return ( ( this.value == null ) ? null : this.value.toString() );
}
@Override
public String type() {
return ( formatType( this.type.getName() ) + collectionTypeSuffix( this.type, this.value ) );
}
@Override
public Object value() {
return this.value;
}
}
}