001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.flatfile.morph; 018 019import java.util.Arrays; 020import java.util.HashSet; 021import java.util.Iterator; 022 023import org.apache.commons.flatfile.Entity; 024import org.apache.commons.flatfile.EntityCollection; 025 026import net.sf.morph.Defaults; 027import net.sf.morph.reflect.IndexedContainerReflector; 028import net.sf.morph.reflect.reflectors.BaseContainerReflector; 029import net.sf.morph.reflect.reflectors.ObjectReflector; 030import net.sf.morph.transform.DecoratedConverter; 031import net.sf.morph.util.ClassUtils; 032 033/** 034 * Base EntityCollection reflector. 035 * @version $Revision: 1301237 $ $Date: 2012-03-15 17:08:23 -0500 (Thu, 15 Mar 2012) $ 036 */ 037public abstract class BaseEntityCollectionReflector extends BaseContainerReflector { 038 /** 039 * Common property accessor logic. 040 */ 041 private abstract class PropertyAccessor { 042 /** 043 * Property name to find 044 */ 045 protected String propertyName; 046 047 /** 048 * Create a new PropertyStrategy instance. 049 * @param propertyName to find 050 */ 051 protected PropertyAccessor(String propertyName) { 052 this.propertyName = propertyName; 053 } 054 055 /** 056 * Get the property type of <code>bean</code>. 057 * @param bean to inspect 058 * @return Class<?> 059 * @throws Exception on error 060 */ 061 final Class<?> getTypeFrom(Object bean) throws Exception { 062 if (isEntity(bean, propertyName)) { 063 return getEntityType(getEntity(bean, propertyName)); 064 } 065 return FALLBACK.getType(bean, propertyName); 066 } 067 068 /** 069 * Strategy template method. 070 * @param entity to inspect 071 * @return Class<?> 072 * @throws Exception on error 073 */ 074 abstract Class<?> getEntityType(Entity entity) throws Exception; 075 076 /** 077 * Read the property. 078 * @param bean to read 079 * @return value 080 * @throws Exception on error 081 */ 082 final Object getFrom(Object bean) throws Exception { 083 if (isEntity(bean, propertyName)) { 084 return getEntityValue(getEntity(bean, propertyName)); 085 } 086 return FALLBACK.get(bean, propertyName); 087 } 088 089 /** 090 * Get the value of the specified Entity. 091 * @param entity to read 092 * @return Object value 093 * @throws Exception on error 094 */ 095 abstract Object getEntityValue(Entity entity) throws Exception; 096 097 /** 098 * Set the property. 099 * @param bean target 100 * @param propertyValue to set 101 * @throws Exception on error 102 */ 103 final void setOn(Object bean, Object propertyValue) throws Exception { 104 if (isEntity(bean, propertyName)) { 105 setEntityValueOn(bean, getEntity(bean, propertyName), propertyValue); 106 } else { 107 FALLBACK.set(bean, propertyName, propertyValue); 108 } 109 } 110 111 /** 112 * Set the entity value. 113 * @param bean target 114 * @param e Entity 115 * @param propertyValue value 116 * @throws Exception on error 117 */ 118 abstract void setEntityValueOn(Object bean, Entity e, Object propertyValue) 119 throws Exception; 120 121 /** 122 * Learn whether our property can be read from a given target. 123 * @param bean to inspect 124 * @return boolean 125 * @throws Exception on error 126 */ 127 final boolean isReadableOn(Object bean) throws Exception { 128 return isEntity(bean, propertyName) || FALLBACK.isReadable(bean, propertyName); 129 } 130 131 /** 132 * Learn whether our property can be written to a given target. 133 * @param bean to inspect 134 * @return boolean 135 * @throws Exception on error 136 */ 137 final boolean isWriteableOn(Object bean) throws Exception { 138 return isEntity(bean, propertyName) || FALLBACK.isWriteable(bean, propertyName); 139 } 140 } 141 142 /** 143 * Overrides the default strategy to treat an Entity as an ordinary javabean. 144 */ 145 private class OverridingPropertyAccessor extends PropertyAccessor { 146 /** 147 * Create a new OverridingPropertyAccessor instance. 148 * @param propertyName String 149 */ 150 public OverridingPropertyAccessor(String propertyName) { 151 super(propertyName.substring(TYPE_OVERRIDE.length())); 152 } 153 154 /** 155 * {@inheritDoc} 156 */ 157 Class<?> getEntityType(Entity e) { 158 return ClassUtils.getClass(e); 159 } 160 161 /** 162 * {@inheritDoc} 163 */ 164 Object getEntityValue(Entity e) { 165 return e; 166 } 167 168 /** 169 * {@inheritDoc} 170 */ 171 void setEntityValueOn(Object bean, Entity e, Object propertyValue) throws Exception { 172 FALLBACK.set(bean, propertyName, propertyValue); 173 } 174 } 175 176 /** 177 * Default {@link PropertyAccessor} 178 */ 179 private class DefaultPropertyAccessor extends PropertyAccessor { 180 /** 181 * Create a new DefaultPropertyAccessor instance. 182 * @param propertyName String 183 */ 184 public DefaultPropertyAccessor(String propertyName) { 185 super(propertyName); 186 } 187 188 /** 189 * {@inheritDoc} 190 */ 191 Class<?> getEntityType(Entity e) { 192 return e instanceof EntityCollection ? EntityCollection.class : String.class; 193 } 194 195 /** 196 * {@inheritDoc} 197 */ 198 Object getEntityValue(Entity e) { 199 return e instanceof EntityCollection ? (Object) e : getString(e); 200 } 201 202 /** 203 * {@inheritDoc} 204 */ 205 void setEntityValueOn(Object bean, Entity e, Object propertyValue) throws Exception { 206 setEntityValue(e, propertyValue); 207 } 208 } 209 210 /** Property name prefix indicating to treat Entities as beans. */ 211 public static final String TYPE_OVERRIDE = "@"; 212 213 private static final ObjectReflector FALLBACK = new ObjectReflector(); 214 215 private DecoratedConverter toTextConverter; 216 private boolean trimStrings = true; 217 218 /** 219 * Get the boolean trimStrings. 220 * @return boolean 221 */ 222 public boolean isTrimStrings() { 223 return trimStrings; 224 } 225 226 /** 227 * Set the boolean trimStrings. 228 * @param trimStrings boolean 229 */ 230 public void setTrimStrings(boolean trimStrings) { 231 this.trimStrings = trimStrings; 232 } 233 234 /** 235 * {@inheritDoc} 236 */ 237 @Override 238 protected Class<?> getContainedTypeImpl(@SuppressWarnings("rawtypes") Class c) throws Exception { 239 return Entity.class; 240 } 241 242 /** 243 * {@inheritDoc} 244 */ 245 @Override 246 protected Iterator<Entity> getIteratorImpl(Object o) throws Exception { 247 return ((EntityCollection) o).getChildren().iterator(); 248 } 249 250 /** 251 * {@inheritDoc} 252 */ 253 @Override 254 protected int getSizeImpl(Object o) throws Exception { 255 return ((EntityCollection) o).getChildren().size(); 256 } 257 258 /** 259 * {@inheritDoc} 260 */ 261 @Override 262 protected Class<?> getTypeImpl(Object bean, String propertyName) throws Exception { 263 return getPropertyAccessor(propertyName).getTypeFrom(bean); 264 } 265 266 /** 267 * {@inheritDoc} 268 */ 269 @Override 270 protected Object getImpl(Object container, int index) throws Exception { 271 Entity e = getEntity(container, index); 272 return e instanceof EntityCollection ? (Object) e : getString(e); 273 } 274 275 /** 276 * {@inheritDoc} 277 */ 278 @Override 279 protected Object getImpl(Object bean, String propertyName) throws Exception { 280 return getPropertyAccessor(propertyName).getFrom(bean); 281 } 282 283 /** 284 * {@inheritDoc} 285 */ 286 @Override 287 protected void setImpl(Object bean, String propertyName, Object value) throws Exception { 288 getPropertyAccessor(propertyName).setOn(bean, value); 289 } 290 291 /** 292 * {@inheritDoc} 293 */ 294 @Override 295 protected Object setImpl(Object container, int index, Object propertyValue) throws Exception { 296 Entity e = getEntity(container, index); 297 byte[] result = e.getValue(); 298 setEntityValue(e, propertyValue); 299 return result; 300 } 301 302 /** 303 * Set the value of an Entity 304 * @param value Object Set the value of <code>e</code> by converting <code>value</code> to a byte[] 305 * @param e Entity 306 * @throws Exception on error 307 */ 308 protected void setEntityValue(Entity e, Object value) throws Exception { 309 e.setValue((byte[]) getToTextConverter().convert(byte[].class, value)); 310 } 311 312 /** 313 * {@inheritDoc} 314 */ 315 @Override 316 protected String[] getPropertyNamesImpl(Object bean) throws Exception { 317 HashSet<String> result = new HashSet<String>(); 318 if (this instanceof IndexedContainerReflector) { 319 result.addAll(Arrays.asList(super.getPropertyNamesImpl(bean))); 320 } 321 String[] fallback = FALLBACK.getPropertyNames(bean); 322 if (fallback != null && fallback.length > 0) { 323 result.addAll(Arrays.asList(fallback)); 324 // add override property names: 325 for (int i = 0; i < fallback.length; i++) { 326 result.add("@" + fallback[i]); 327 } 328 } 329 return result.toArray(new String[result.size()]); 330 } 331 332 /** 333 * Learn whether the specified property refers to an Entity child. 334 * @param bean to inspect 335 * @param propertyName to read 336 * @return boolean 337 * @throws Exception on error 338 */ 339 protected abstract boolean isEntity(Object bean, String propertyName) throws Exception; 340 341 /** 342 * Get the named child entity. 343 * @param bean to read 344 * @param propertyName to read 345 * @return Entity 346 * @throws Exception on error 347 */ 348 protected Entity getEntity(Object bean, String propertyName) throws Exception { 349 return getEntity(bean, Integer.parseInt(propertyName)); 350 } 351 352 /** 353 * Get the child entity at index <code>index</code>. 354 * Base implementation throws {@link UnsupportedOperationException}. 355 * @param container to read 356 * @param index to get 357 * @return Entity 358 * @throws Exception on error 359 */ 360 protected Entity getEntity(Object container, int index) throws Exception { 361 throw new UnsupportedOperationException(); 362 } 363 364 /** 365 * {@inheritDoc} 366 */ 367 @Override 368 protected boolean isReadableImpl(Object bean, String propertyName) throws Exception { 369 return getPropertyAccessor(propertyName).isReadableOn(bean); 370 } 371 372 /** 373 * {@inheritDoc} 374 */ 375 @Override 376 protected boolean isWriteableImpl(Object bean, String propertyName) throws Exception { 377 return getPropertyAccessor(propertyName).isWriteableOn(bean); 378 } 379 380 /** 381 * Get the to-text {@link DecoratedConverter} assigned. 382 * @return the toTextConverter 383 */ 384 public synchronized DecoratedConverter getToTextConverter() { 385 if (toTextConverter == null) { 386 setToTextConverter(Defaults.createToTextConverter()); 387 } 388 return toTextConverter; 389 } 390 391 /** 392 * Set the to-text {@link DecoratedConverter}. 393 * @param toTextConverter to set 394 */ 395 public synchronized void setToTextConverter(DecoratedConverter toTextConverter) { 396 this.toTextConverter = toTextConverter; 397 } 398 399 /** 400 * Get a PropertyAccessor instance to handle this property. 401 * @param propertyName to handle 402 * @return PropertyStrategy 403 */ 404 private PropertyAccessor getPropertyAccessor(String propertyName) { 405 return propertyName.startsWith(TYPE_OVERRIDE) ? (PropertyAccessor) new OverridingPropertyAccessor( 406 propertyName) 407 : new DefaultPropertyAccessor(propertyName); 408 } 409 410 /** 411 * Get the value of the specified Entity as a String, respecting this {@link BaseEntityCollectionReflector}'s 412 * trimStrings property. 413 * @param e Entity 414 * @return e's value as a String. 415 */ 416 private String getString(Entity e) { 417 String s = new String(e.getValue()); 418 return isTrimStrings() ? s.trim() : s; 419 } 420 421}