/* * Copyright 2010 Google Inc. * * 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 com.google.web.bindery.autobean.shared.impl; import com.google.gwt.core.client.impl.WeakMapping; import com.google.web.bindery.autobean.shared.AutoBean; import com.google.web.bindery.autobean.shared.AutoBeanFactory; import com.google.web.bindery.autobean.shared.AutoBeanUtils; import com.google.web.bindery.autobean.shared.AutoBeanVisitor; import com.google.web.bindery.autobean.shared.AutoBeanVisitor.Context; import com.google.web.bindery.autobean.shared.Splittable; import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.Coder; import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.EncodeState; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Basic implementation. * * @param <T> the wrapper type */ public abstract class AbstractAutoBean<T> implements AutoBean<T>, HasSplittable { /** * Used to avoid cycles when visiting. */ public static class OneShotContext implements Context { private final Set<AbstractAutoBean<?>> seen = new HashSet<AbstractAutoBean<?>>(); public boolean hasSeen(AbstractAutoBean<?> bean) { return !seen.add(bean); } } public static final String UNSPLITTABLE_VALUES_KEY = "__unsplittableValues"; protected static final Object[] EMPTY_OBJECT = new Object[0]; /** * Used by {@link #createSimplePeer()}. */ protected Splittable data; protected T wrapped; private final AutoBeanFactory factory; private boolean frozen; /** * Lazily initialized by {@link #setTag(String, Object)} because not all * instances will make use of tags. */ private Map<String, Object> tags; private final boolean usingSimplePeer; /** * Constructor that will use a generated simple peer. */ protected AbstractAutoBean(AutoBeanFactory factory) { this(factory, StringQuoter.createSplittable()); } /** * Constructor that will use a generated simple peer, backed with existing * data. */ protected AbstractAutoBean(AutoBeanFactory factory, Splittable data) { this.data = data; this.factory = factory; usingSimplePeer = true; wrapped = createSimplePeer(); } /** * Constructor that wraps an existing object. The parameters on this method * are reversed to avoid conflicting with the other two-arg constructor for * {@code AutoBean<Splittable>} instances. */ protected AbstractAutoBean(T wrapped, AutoBeanFactory factory) { this.factory = factory; usingSimplePeer = false; data = null; this.wrapped = wrapped; // Used by AutoBeanUtils WeakMapping.setWeak(wrapped, AutoBean.class.getName(), this); } public void accept(AutoBeanVisitor visitor) { traverse(visitor, new OneShotContext()); } public abstract T as(); public AutoBean<T> clone(boolean deep) { throw new UnsupportedOperationException(); } public AutoBeanFactory getFactory() { return factory; } public Splittable getSplittable() { return data; } @SuppressWarnings("unchecked") public <Q> Q getTag(String tagName) { return tags == null ? null : (Q) tags.get(tagName); } /** * Indicates that the value returned from {@link #getSplittable()} may not * contain all of the data encapsulated by the AutoBean. */ public boolean hasUnsplittableValues() { return data.isReified(UNSPLITTABLE_VALUES_KEY); } public boolean isFrozen() { return frozen; } public boolean isWrapper() { return !usingSimplePeer; } public void setData(Splittable data) { assert data != null : "null data"; this.data = data; /* * The simple peer aliases the data object from the enclosing bean to avoid * needing to call up the this.this$0 chain. */ wrapped = createSimplePeer(); } public void setFrozen(boolean frozen) { this.frozen = frozen; } public void setTag(String tagName, Object value) { if (tags == null) { tags = new HashMap<String, Object>(); } tags.put(tagName, value); } public void traverse(AutoBeanVisitor visitor, OneShotContext ctx) { // Avoid cycles if (ctx.hasSeen(this)) { return; } if (visitor.visit(this, ctx)) { traverseProperties(visitor, ctx); } visitor.endVisit(this, ctx); } public T unwrap() { if (usingSimplePeer) { throw new IllegalStateException(); } try { WeakMapping.set(wrapped, AutoBean.class.getName(), null); return wrapped; } finally { wrapped = null; } } /** * No-op. Used as a debugger hook point for generated code. * * @param method the method name * @param returned the returned object * @param parameters the parameter list */ protected void call(String method, Object returned, Object... parameters) { } protected void checkFrozen() { if (frozen) { throw new IllegalStateException("The AutoBean has been frozen"); } } protected void checkWrapped() { if (wrapped == null && !usingSimplePeer) { throw new IllegalStateException("The AutoBean has been unwrapped"); } } protected T createSimplePeer() { throw new UnsupportedOperationException(); } /** * No-op. Used as a debugger hook point for generated code. * * @param method the method name * @param toReturn the value to return */ protected <V> V get(String method, V toReturn) { return toReturn; } protected <W> W getFromWrapper(W obj) { // Some versions of javac have problem inferring the generics here return AutoBeanUtils.<W, W> getAutoBean(obj).as(); } /** * Native getters and setters for primitive properties are generated for each * type to ensure inlining. */ protected <Q> Q getOrReify(String propertyName) { checkWrapped(); if (data.isReified(propertyName)) { @SuppressWarnings("unchecked") Q temp = (Q) data.getReified(propertyName); return temp; } if (data.isNull(propertyName)) { return null; } data.setReified(propertyName, null); Coder coder = AutoBeanCodexImpl.doCoderFor(this, propertyName); @SuppressWarnings("unchecked") Q toReturn = (Q) coder.decode(EncodeState.forDecode(factory), data.get(propertyName)); data.setReified(propertyName, toReturn); return toReturn; } protected T getWrapped() { checkWrapped(); return wrapped; } protected boolean isUsingSimplePeer() { return usingSimplePeer; } protected boolean isWrapped(Object obj) { return AutoBeanUtils.getAutoBean(obj) != null; } /** * No-op. Used as a debugger hook point for generated code. * * @param method the method name * @param value the Object value to be set */ protected void set(String method, Object value) { } protected void setProperty(String propertyName, Object value) { checkWrapped(); checkFrozen(); data.setReified(propertyName, value); if (value == null) { Splittable.NULL.assign(data, propertyName); return; } Coder coder = AutoBeanCodexImpl.doCoderFor(this, propertyName); Splittable backing = coder.extractSplittable(EncodeState.forDecode(factory), value); if (backing == null) { /* * External data type, such as an ArrayList or a concrete implementation * of a setter's interface type. This means that a slow serialization pass * is necessary. */ data.setReified(UNSPLITTABLE_VALUES_KEY, true); } else { backing.assign(data, propertyName); } } protected abstract void traverseProperties(AutoBeanVisitor visitor, OneShotContext ctx); }