Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
atom.view.AbstractView Maven / Gradle / Ivy
/*
* Copyright © 2015 Geeoz, and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Research Projects is dual-licensed under the GNU General Public
* License, version 2.0 (GPLv2) and the Geeoz Commercial License.
*
* Solely for non-commercial purposes. A purpose is non-commercial only if
* it is in no manner primarily intended for or directed toward commercial
* advantage or private monetary compensation.
*
* This Geeoz Software is supplied to you by Geeoz in consideration of your
* agreement to the following terms, and your use, installation, modification
* or redistribution of this Geeoz Software constitutes acceptance of these
* terms. If you do not agree with these terms, please do not use, install,
* modify or redistribute this Geeoz Software.
*
* Neither the name, trademarks, service marks or logos of Geeoz may be used
* to endorse or promote products derived from the Geeoz Software without
* specific prior written permission from Geeoz.
*
* The Geeoz Software is provided by Geeoz on an "AS IS" basis. GEEOZ MAKES NO
* WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED
* WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE, REGARDING THE GEEOZ SOFTWARE OR ITS USE AND OPERATION ALONE OR IN
* COMBINATION WITH YOUR PRODUCTS.
*
* IN NO EVENT SHALL GEEOZ BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION
* AND/OR DISTRIBUTION OF THE GEEOZ SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER
* THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
* OTHERWISE, EVEN IF GEEOZ HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* A copy of the GNU General Public License is included in the distribution in
* the file LICENSE and at
*
* http://www.gnu.org/licenses/gpl-2.0.html
*
* If you are using the Research Projects for commercial purposes, we
* encourage you to visit
*
* http://products.geeoz.com/license
*
* for more details.
*
* This software or hardware and documentation may provide access to
* or information on content, products, and services from third parties.
* Geeoz and its affiliates are not responsible for and expressly disclaim
* all warranties of any kind with respect to third-party content, products,
* and services. Geeoz and its affiliates will not be responsible for any loss,
* costs, or damages incurred due to your access to or use of third-party
* content, products, or services. If a third-party content exists, the
* additional copyright notices and license terms applicable to portions of the
* software are set forth in the THIRD_PARTY_LICENSE_README file.
*
* Please contact Geeoz or visit www.geeoz.com if you need additional
* information or have any questions.
*/
package atom.view;
import atom.beans.event.AbstractInsertChildPropertyChangeEvent;
import atom.beans.event.AbstractPropertyChangeEvent;
import atom.beans.event.InsertChildPropertyChangeEvent;
import atom.beans.event.InsertFirstChildPropertyChangeEvent;
import atom.beans.event.MultiplePropertyChangeEvent;
import atom.beans.event.RemoveChildPropertyChangeEvent;
import atom.beans.event.SimplePropertyChangeEvent;
import atom.view.constants.ViewConstants;
import atom.widget.AnchorView;
import atom.widget.Button;
import atom.widget.CheckBox;
import atom.widget.DateView;
import atom.widget.EditText;
import atom.widget.GalleryView;
import atom.widget.GoogleAutocompleteView;
import atom.widget.ImageView;
import atom.widget.LabelView;
import atom.widget.LinearLayout;
import atom.widget.ListView;
import atom.widget.LoadingView;
import atom.widget.MapPointView;
import atom.widget.MapView;
import atom.widget.RadioButton;
import atom.widget.RadioGroup;
import atom.widget.Slider;
import atom.widget.Spinner;
import atom.widget.TextView;
import atom.widget.UploadView;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import scala.Some;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* This abstract class represents the basic change tracking support.
*
* @author Alex Voloshyn
* @author Serge Voloshyn
* @author Vladimir Ovcharov
* @version 1.21 4/18/15
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY, property = "jso")
@JsonSubTypes({
@JsonSubTypes.Type(value = View.class, name = "View"),
@JsonSubTypes.Type(value = Window.class, name = "Window"),
@JsonSubTypes.Type(value = MapPointView.class, name = "MapPointView"),
@JsonSubTypes.Type(value = LoadingView.class, name = "LoadingView"),
@JsonSubTypes.Type(value = Slider.class, name = "Slider"),
@JsonSubTypes.Type(value = DateView.class, name = "DateView"),
@JsonSubTypes.Type(value = GoogleAutocompleteView.class,
name = "GoogleAutocompleteView"),
@JsonSubTypes.Type(value = AnchorView.class, name = "AnchorView"),
@JsonSubTypes.Type(value = UploadView.class, name = "UploadView"),
@JsonSubTypes.Type(value = ImageView.class, name = "ImageView"),
@JsonSubTypes.Type(value = LabelView.class, name = "LabelView"),
@JsonSubTypes.Type(value = RadioButton.class, name = "RadioButton"),
@JsonSubTypes.Type(value = CheckBox.class, name = "CheckBox"),
@JsonSubTypes.Type(value = Button.class, name = "Button"),
@JsonSubTypes.Type(value = EditText.class, name = "EditText"),
@JsonSubTypes.Type(value = TextView.class, name = "TextView"),
@JsonSubTypes.Type(value = RadioGroup.class, name = "RadioGroup"),
@JsonSubTypes.Type(value = LinearLayout.class, name = "LinearLayout"),
@JsonSubTypes.Type(value = GalleryView.class, name = "GalleryView"),
@JsonSubTypes.Type(value = ListView.class, name = "ListView"),
@JsonSubTypes.Type(value = MapView.class, name = "MapView"),
@JsonSubTypes.Type(value = Spinner.class, name = "Spinner"),
@JsonSubTypes.Type(value = ViewGroup.class, name = "ViewGroup")
})
public abstract class AbstractView implements Serializable, ViewSearch {
/**
* Use serialVersionUID from JDK 1.0.2 for interoperability.
*/
private static final long serialVersionUID = -150771344243581601L;
/**
* JAXB manifest namespace.
*/
public static final String LAYOUT_NAMESPACE =
"http://atom.geeoz.com/layout/1.0";
/**
* The view's identifier.
*/
private String viewId;
/**
* Binary service's path responsible to file uploading.
*/
private String binaryPath;
/**
* Binary service's attributes.
*/
private List binaryAttributes = new ArrayList<>();
/**
* Additional options for the view.
*/
private List options = new ArrayList<>();
/**
* Property change support for current view.
*/
private transient PropertyChangeSupport support = null;
/**
* Is tracking now state.
*/
private transient boolean isTrackingNow = false;
/**
* Parent tracking entity.
*/
private transient AbstractView parent = null;
/**
* List with property change events.
*/
private transient Map changes = null;
/**
* Retrieve underlying resource controller.
*/
private transient Controller controller = null;
/**
* Simple constructor to use when creating a view from code.
*/
public AbstractView() {
super();
}
/**
* Simple constructor that creates View
with defined
* identifier.
*
* @param identify a string used to identify the view
* @see #setViewId(String)
*/
public AbstractView(final String identify) {
super();
viewId = identify;
}
/**
* Returns this view's identifier.
*
* @return a string used to identify the view or null
if the
* view has no ID
*/
@XmlAttribute(name = "id")
@JsonProperty("viewId")
public final String getViewId() {
return viewId;
}
/**
* Sets the identifier for this view. The identifier does not have to be
* unique in this view's hierarchy.
*
* @param identify a string used to identify the view
*/
public final void setViewId(final String identify) {
viewId = identify;
}
/**
* Get binary service path responsible to file uploading.
*
* @return binary service path
*/
@XmlAttribute(name = "binary-path")
@JsonProperty("binaryPath")
public final String getBinaryPath() {
if (binaryPath == null && getParent() != null) {
return getParent().getBinaryPath();
}
return binaryPath;
}
/**
* Set the binary service path.
*
* @param aBinaryPath new binary service path
*/
public final void setBinaryPath(final String aBinaryPath) {
binaryPath = aBinaryPath;
}
/**
* Add the binary service attribute to the view instance.
*
* @param name attribute's name
* @param value attribute's value
*/
public final void addBinaryAttribute(final String name,
final String value) {
final Option attr = new Option();
attr.setName(name);
attr.setValue(filter(value));
binaryAttributes.add(attr);
}
/**
* Retrieve an options of the view.
*
* @return list of the options
*/
@XmlElement(name = "BinaryAttribute",
namespace = AbstractView.LAYOUT_NAMESPACE)
@JsonProperty("binaryAttributes")
public final List getBinaryAttributes() {
if (binaryAttributes == null || binaryAttributes.isEmpty()
&& getParent() != null) {
return getParent().getBinaryAttributes();
}
return binaryAttributes;
}
/**
* Sets the binary service's attributes of the view.
*
* @param list list of the options
*/
public final void setBinaryAttributes(final List list) {
binaryAttributes = list;
}
/**
* Add the option to the view instance.
*
* @param option the option
*/
public final void addOption(final Option option) {
options.add(option);
}
/**
* Retrieve an options of the view.
*
* @return list of the options
*/
@XmlElement(name = "Option", namespace = AbstractView.LAYOUT_NAMESPACE)
@JsonProperty("options")
public final List getOptions() {
return options;
}
/**
* Sets an options of the view.
*
* @param list list of the options
*/
public final void setOptions(final List list) {
options = list;
fireChange(ViewConstants.OPTIONS_NAME, options);
}
/**
* Check that string could be a reference.
*
* @param string input string for check
* @return true if string is the reference
*/
private boolean isReference(final String string) {
return !(string == null || string.isEmpty()) && string.charAt(0) == '@';
}
/**
* Assign the parent if it exists, or null.
*
* @param object the parent tracking object
*/
protected final void assignParent(final AbstractView object) {
parent = object;
}
/**
* Return the parent if it exists, or null.
*
* @return the parent
*/
@JsonBackReference
public final AbstractView getParent() {
return parent;
}
/**
* Получение первого не пустого значения идентификатора представления
* прородителя.
*
* @return идентификатор представления прородителя либо null
*/
protected final String getParentId() {
if (parent == null) {
return null;
} else if (parent.getViewId() != null) {
return parent.getViewId();
}
return parent.getParentId();
}
/**
* Обновление идентификатора представления с учетом прородителя.
*/
protected final void unifyId() {
final String parentId = getParentId();
if (parentId != null) {
setViewId(viewId + '_' + parentId);
}
}
/**
* Parent object is not restored on unmarshal, so this method should
* help.
*
* @param unmarshaller an instance of the unmarshaller
* @param object parent object
*/
@SuppressWarnings("unused")
public final void afterUnmarshal(final Unmarshaller unmarshaller,
final Object object) {
assignParent((AbstractView) object);
}
/**
* Retrieve view that corresponding for change tracking.
*
* @return corresponding trackable view
*/
private AbstractView root() {
if (parent != null) {
return parent.root();
}
return this;
}
/**
* Build default property change support instance.
*
* @return new property change support instance.
*/
private PropertyChangeSupport getChangeSupport() {
final PropertyChangeSupport instance = new PropertyChangeSupport(this);
instance.addPropertyChangeListener(new DefaultChangeListener());
return instance;
}
/**
* Apply changes to view according to existing hierarchy.
*
* @param event property change event that should be applied
*/
public final void applyChange(final PropertyChangeEvent event) {
if (event == null) {
return;
}
final String propertyName = event.getPropertyName();
if (!(event instanceof AbstractPropertyChangeEvent)
|| event instanceof SimplePropertyChangeEvent
&& !Objects.equals(propertyName, ViewConstants.CHILDREN_NAME)) {
try {
final Field field = getField(getClass(), propertyName);
AccessController.doPrivileged(
new AccessiblePrivilegedAction(field));
final Object value = event.getNewValue();
if (root().controller != null
&& value instanceof String) {
final Object old = field.get(this);
if (old instanceof String) {
final String oldString = (String) old;
final String string = (String) value;
field.set(this, update(oldString, string));
} else {
field.set(this, value);
}
} else {
field.set(this, value);
}
} catch (ReflectiveOperationException fault) {
fault.printStackTrace();
}
} else if (event instanceof MultiplePropertyChangeEvent) {
((MultiplePropertyChangeEvent) event)
.getEvents().forEach(this::applyChange);
} else if (this instanceof ViewGroup) {
applyChildrenChange(event);
}
}
/**
* Apply view group change.
*
* @param event view group change
*/
@SuppressWarnings("unchecked")
private void applyChildrenChange(final PropertyChangeEvent event) {
final ViewGroup group = (ViewGroup) this;
final ViewGroup.ViewLinkedList groupChildren = group.getChildren();
if (event instanceof SimplePropertyChangeEvent) {
groupChildren.clear();
groupChildren.addAll(
(Collection extends View>) event.getNewValue());
return;
} else if (event instanceof RemoveChildPropertyChangeEvent) {
final List childIds
= ((RemoveChildPropertyChangeEvent) event).getChildIds();
final Iterator iterator = groupChildren.descendingIterator();
while (!childIds.isEmpty() && iterator.hasNext()) {
if (childIds.remove(iterator.next().getViewId())) {
iterator.remove();
}
}
return;
} else if (!(event instanceof AbstractInsertChildPropertyChangeEvent)) {
return;
}
final int insertingIndex;
if (event instanceof InsertFirstChildPropertyChangeEvent) {
insertingIndex = 0;
} else if (event instanceof InsertChildPropertyChangeEvent) {
final InsertChildPropertyChangeEvent insert
= (InsertChildPropertyChangeEvent) event;
insertingIndex = group.findChildViewIndexById(insert.getBefore());
} else {
insertingIndex = groupChildren.size();
}
groupChildren.addAll(insertingIndex,
((AbstractInsertChildPropertyChangeEvent) event)
.getChildren());
}
/**
* Get field from class or super class.
*
* @param clazz class for field access
* @param fieldName name for search
* @return a field that was fined
* @throws NoSuchFieldException in case if field wasn't find
*/
private Field getField(final Class> clazz,
final String fieldName) throws NoSuchFieldException {
try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException fault) {
final Class> superClass = clazz.getSuperclass();
if (superClass == null) {
throw fault;
} else {
return getField(superClass, fieldName);
}
}
}
/**
* Apply changes to view according to existing hierarchy.
*
* @param events property change events that should be applied
*/
public final void applyChanges(final List events) {
if (events == null) {
return;
}
for (PropertyChangeEvent event : events) {
final View view = findViewById((String) event.getPropagationId());
if (view != null) {
view.applyChange(event);
}
}
}
/**
* Provide list of property change events.
*
* @return list of property change events
*/
@XmlTransient
@JsonIgnore
public final List getChanges() {
if (root().changes == null) {
return null;
} else {
final List events =
new ArrayList<>(root().changes.values());
root().changes.clear();
return events;
}
}
/**
* Returns current tracking status.
*
* @return current tracking status
*/
@XmlTransient
@JsonIgnore
public final boolean isTracking() {
return isTrackingNow;
}
/**
* Tests if this object has changed.
*
* @return true
if the object changes are not empty.
*/
public final boolean hasChanged() {
final Map events = root().changes;
return !(events == null || events.isEmpty());
}
/**
* Reports a bound property update to listeners that have been registered to
* track updates of all properties or a property with the specified name.
*
* @param propertyName the programmatic name of the property that was
* changed
* @param newValue the new value of the property
*/
protected final void fireChange(final String propertyName,
final Object newValue) {
if (!root().isTrackingNow) {
return;
}
final AbstractPropertyChangeEvent event;
if (newValue instanceof String) {
event = new SimplePropertyChangeEvent(
this, propertyName, null, filter((String) newValue));
} else {
event = new SimplePropertyChangeEvent(
this, propertyName, null, newValue);
}
fireChange(event);
}
/**
* Reports a bound property update to listeners that have been registered to
* track updates of all properties or a property with the specified name.
*
* @param event an event
*/
protected final void fireChange(final AbstractPropertyChangeEvent event) {
if (!root().isTrackingNow) {
return;
}
event.setPropagationId(viewId);
if (support == null) {
support = getChangeSupport();
}
support.firePropertyChange(event);
}
/**
* Enable tracking changes for the view.
*/
public final void trackChanges() {
trackChanges(true);
}
/**
* Set status for tracking changes for the view.
*
* @param enable status for change tracking
*/
public final void trackChanges(final boolean enable) {
trackChanges(enable, true);
}
/**
* Set status for tracking changes for the view.
*
* @param enable status for change tracking
* @param clearExisting whether to clear existing changes or not
*/
public final void trackChanges(final boolean enable,
final boolean clearExisting) {
final AbstractView view = root();
view.isTrackingNow = enable;
final Map events = view.changes;
if (clearExisting && !enable && events != null) {
events.clear();
}
}
/**
* Sets resource controller reference.
*
* @param resources a resource controller
*/
public final void setController(final Controller resources) {
controller = resources;
}
/**
* Получение собственного контроллера или в случае его отсутствия, получение
* первого контроллера по иерархии предков.
*
* @return собственный контроллер или контроллер предка
*/
@XmlTransient
public final Controller getController() {
if (controller == null && parent != null) {
return parent.getController();
}
return controller;
}
/**
* Return the string value associated with a particular resource reference.
* It will be stripped of any styled text information.
*
* @param string the desired resource identifier
* @return the string data associated with the resource, stripped of styled
* text information or input reference if no value exists
*/
protected final String filter(final String string) {
if (!isReference(string)) {
return string;
}
final Controller resources = getController();
if (resources == null) {
return string;
}
final scala.Option value = resources.filter(string);
if (value instanceof Some) {
return value.get();
}
return string;
}
/**
* Return a reference or updated value.
*
* @param oldValue old view value
* @param newValue new view value
* @return a reference or updated value
*/
protected final String update(final String oldValue,
final String newValue) {
if (!isReference(oldValue)) {
return newValue;
}
final Controller resources = getController();
if (resources == null || resources.isReadOnly()) {
return newValue;
}
resources.update(oldValue, newValue);
return oldValue;
}
/**
* Default property change listener.
*/
@XmlTransient
private class DefaultChangeListener implements PropertyChangeListener {
@Override
public final void propertyChange(final PropertyChangeEvent evt) {
Map events = root().changes;
if (events == null) {
events = new HashMap<>();
root().changes = events;
}
final String key = String.valueOf(
evt.getPropagationId()).concat(evt.getPropertyName());
final PropertyChangeEvent existing = events.get(key);
if (existing == null
|| !(existing instanceof AbstractPropertyChangeEvent)
|| !(evt instanceof AbstractPropertyChangeEvent)) {
events.put(key, evt);
} else {
events.put(key,
((AbstractPropertyChangeEvent) existing).merge(evt));
}
}
}
/**
* Provide accessible change.
*/
@XmlTransient
private static class AccessiblePrivilegedAction
implements PrivilegedAction {
/**
* Field for accessible change.
*/
private final transient Field field;
/**
* @param aField field to change accessible
*/
AccessiblePrivilegedAction(final Field aField) {
field = aField;
}
@Override
public Void run() {
field.setAccessible(true);
return null; // nothing to return
}
}
/**
* An additional property of the view.
*/
@XmlType(namespace = AbstractView.LAYOUT_NAMESPACE)
public static final class Option implements Serializable {
/**
* Use serialVersionUID from JDK 1.0.2 for interoperability.
*/
private static final long serialVersionUID = 4759745887860389723L;
/**
* Name of the option.
*/
private String name;
/**
* Value of the option.
*/
private String value;
/**
* Retrieve name of the option.
*
* @return the name of the option
*/
@XmlAttribute(name = "name")
public String getName() {
return name;
}
/**
* Sets name of the option.
*
* @param string the name of the option
*/
public void setName(final String string) {
name = string;
}
/**
* Retrieve value of the option.
*
* @return the value of the option
*/
@XmlAttribute(name = "value")
public String getValue() {
return value;
}
/**
* Sets value of the option.
*
* @param string the value of the option
*/
public void setValue(final String string) {
value = string;
}
@Override
public boolean equals(final Object object) {
if (this == object) {
return true;
}
if (object == null || object.getClass() != getClass()) {
return false;
}
final Option that = (Option) object;
return Objects.equals(name, that.name)
&& Objects.equals(value, that.value);
}
@Override
public int hashCode() {
return Objects.hash(name, value);
}
}
}