/* * The MIT License * * Copyright (c) 2011, Oracle Corporation, Nikita Levyankov * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.hudsonci.model.project.property; import hudson.util.DeepEquals; import org.apache.commons.lang3.ObjectUtils; import org.hudsonci.api.model.ICascadingJob; import org.hudsonci.api.model.IProjectProperty; /** * Base property implementation for project. * Contains common methods for setting and getting cascading and overridden properties. * <p/> * Date: 9/22/11 * * @author Nikita Levyankov */ public class BaseProjectProperty<T> implements IProjectProperty<T> { static final String INVALID_JOB_EXCEPTION = "Project property should have not null job"; static final String INVALID_PROPERTY_KEY_EXCEPTION = "Project property should have not null propertyKey"; private transient String propertyKey; private transient ICascadingJob job; private T originalValue; private boolean propertyOverridden; /** * Instantiate new property. * * @param job owner of current property. */ public BaseProjectProperty(ICascadingJob job) { setJob(job); } /** * {@inheritDoc} */ public void setKey(String propertyKey) { this.propertyKey = propertyKey; } /** * {@inheritDoc} */ public String getKey() { return propertyKey; } /** * {@inheritDoc} */ public void setJob(ICascadingJob job) { if (null == job) { throw new IllegalArgumentException(INVALID_JOB_EXCEPTION); } this.job = job; } /** * @return job that property belongs to. */ final ICascadingJob getJob() { return job; } /** * {@inheritDoc} */ public void setOverridden(boolean overridden) { propertyOverridden = overridden; } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") public T getCascadingValue() { if (null == getKey()) { throw new IllegalArgumentException(INVALID_PROPERTY_KEY_EXCEPTION); } return getJob().hasCascadingProject() ? (T) getJob().getCascadingProject().getProperty(propertyKey, this.getClass()).getValue() : getDefaultValue(); } /** * {@inheritDoc} */ public boolean isOverridden() { return propertyOverridden; } /** * {@inheritDoc} */ public T getDefaultValue() { return null; } /** * {@inheritDoc} */ public T getValue() { if (returnOriginalValue()) { return getOriginalValue(); } return getCascadingValue(); } /** * Checks whether original or cascading value should be used. * * @return true to use original value, false - cascading value. */ protected boolean returnOriginalValue() { return isOverridden() || null != originalValue; } /** * {@inheritDoc} */ @SuppressWarnings("unchecked") public void setValue(T value) { if (null == getKey()) { throw new IllegalArgumentException(INVALID_PROPERTY_KEY_EXCEPTION); } value = prepareValue(value); if (!getJob().hasCascadingProject()) { setOriginalValue(value, false); } else { updateOriginalValue(value, getCascadingValue()); } } /** * Update value for cascading property. Before setting new value it will be checked for equality with cascading * value. If two values are equals - current value will be cleared via {@link #clearOriginalValue(Object)} * and will retrieved from parent. If not - value will be set directly. * * @param value new value to be set. * @param cascadingValue current cascading value. * @return true - if property was updated, false - otherwise if value was cleared. */ protected boolean updateOriginalValue(T value, T cascadingValue) { T candidateValue = null == value ? getDefaultValue() : value; if (allowOverrideValue(cascadingValue, candidateValue)) { setOriginalValue(value, true); return true; } else { clearOriginalValue(value); return false; } } /** * Method that sets original value and mark it as overridden if needed. It was created to provide better flexibility * in subclasses. * * @param originalValue value to set * @param overridden true - to mark as overridden. */ protected void setOriginalValue(T originalValue, boolean overridden) { this.originalValue = originalValue; setOverridden(overridden); } /** * Method that clears original value and marks it as overridden if needed. * Default implementation uses {@link #resetValue()}. Subclasses can override this method. * * @param originalValue value to set. */ protected void clearOriginalValue(T originalValue) { resetValue(); } /** * {@inheritDoc} */ public void resetValue() { setOriginalValue(null, false); } /** * {@inheritDoc} */ public boolean allowOverrideValue(T cascadingValue, T candidateValue) { return ObjectUtils.notEqual(cascadingValue, candidateValue) && !DeepEquals.deepEquals(cascadingValue, candidateValue); } /** * Pre-process candidate value. * * @param candidateValue candidateValue. * @return candidateValue by default. */ protected T prepareValue(T candidateValue) { return candidateValue; } /** * {@inheritDoc} */ public T getOriginalValue() { return originalValue; } /** * {@inheritDoc} */ public final void onCascadingProjectChanged() { if (getJob().hasCascadingProject()) { onCascadingProjectSet(); } else { onCascadingProjectRemoved(); } } /** * Executes when cascading parent is cleared. Default implementation marks property as not overridden. */ protected void onCascadingProjectRemoved() { setOverridden(false); } /** * Executes when cascading project is set. Default implementation compares cascading and current value. * If values are not equal - mark property as overridden. */ protected void onCascadingProjectSet() { setOverridden(allowOverrideValue(getCascadingValue(), getValue())); } }