/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program 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
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.operator.ports.impl;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import com.rapidminer.RapidMiner;
import com.rapidminer.gui.RapidMinerGUI;
import com.rapidminer.gui.renderer.RendererService;
import com.rapidminer.operator.IOObject;
import com.rapidminer.operator.PortUserError;
import com.rapidminer.operator.UserError;
import com.rapidminer.operator.ports.IncompatibleMDClassException;
import com.rapidminer.operator.ports.Port;
import com.rapidminer.operator.ports.Ports;
import com.rapidminer.operator.ports.metadata.MetaData;
import com.rapidminer.operator.ports.metadata.MetaDataError;
import com.rapidminer.operator.ports.quickfix.QuickFix;
import com.rapidminer.tools.AbstractObservable;
import com.rapidminer.tools.ReferenceCache;
/**
* Implemented by keeping a weak reference to the data that can be cleared at any time by the
* garbage collector.
*
* In addition to the week reference, this class also keeps a hard reference to the data, freeing it
* when calling {@link #freeMemory()}.
*
* @author Simon Fischer
*
*/
public abstract class AbstractPort extends AbstractObservable<Port> implements Port {
private final List<MetaDataError> errorList = new LinkedList<>();
private final Ports<? extends Port> ports;
private String name;
private static final ReferenceCache<IOObject> IOO_REFERENCE_CACHE = new ReferenceCache<>(20);
private ReferenceCache<IOObject>.Reference weakDataReference;
private IOObject hardDataReference;
private final boolean simulatesStack;
private boolean locked = false;
protected AbstractPort(Ports<? extends Port> owner, String name, boolean simulatesStack) {
this.name = name;
this.ports = owner;
this.simulatesStack = simulatesStack;
}
protected final void setData(IOObject object) {
// if there is a (G)UI and it is not in background => cache
if (!RapidMiner.getExecutionMode().isHeadless() && ports.getOwner() != null && ports.getOwner().getOperator() != null
&& ports.getOwner().getOperator().getProcess() != null && ports.getOwner().getOperator().getProcess()
.getRootOperator().getUserData(RapidMinerGUI.IS_GUI_PROCESS) != null) {
this.weakDataReference = IOO_REFERENCE_CACHE.newReference(object);
}
this.hardDataReference = object;
}
@Deprecated
@Override
public <T extends IOObject> T getData() throws UserError {
T data = this.<T> getDataOrNull();
if (data == null) {
throw new PortUserError(this, 149, getSpec() + (isConnected() ? " (connected)" : " (disconnected)"));
} else {
return data;
}
}
@Override
public IOObject getAnyDataOrNull() {
if (hardDataReference != null) {
return hardDataReference;
} else {
// This method is invoked from many places that should not keep the cache entry warm
// (e.g., visualizations). Thus, perform only a weak get.
return this.weakDataReference != null ? this.weakDataReference.weakGet() : null;
}
}
@Override
public <T extends IOObject> T getData(Class<T> desiredClass) throws UserError {
IOObject data = getAnyDataOrNull();
if (data == null) {
throw new PortUserError(this, 149, getSpec() + (isConnected() ? " (connected)" : " (disconnected)"));
} else if (desiredClass.isAssignableFrom(data.getClass())) {
return desiredClass.cast(data);
} else {
PortUserError error = new PortUserError(this, 156, RendererService.getName(data.getClass()), this.getName(),
RendererService.getName(desiredClass));
error.setExpectedType(desiredClass);
error.setActualType(data.getClass());
throw error;
}
}
@Override
public <T extends IOObject> T getDataOrNull(Class<T> desiredClass) throws UserError {
IOObject data = getAnyDataOrNull();
if (data == null) {
return null;
} else if (desiredClass.isAssignableFrom(data.getClass())) {
return desiredClass.cast(data);
} else {
PortUserError error = new PortUserError(this, 156, RendererService.getName(data.getClass()), this.getName(),
RendererService.getName(desiredClass));
error.setExpectedType(desiredClass);
error.setActualType(data.getClass());
throw error;
}
}
@SuppressWarnings("unchecked")
@Deprecated
@Override
public <T extends IOObject> T getDataOrNull() throws UserError {
IOObject data = getAnyDataOrNull();
return (T) data;
}
@Override
public final String getName() {
return name;
}
@Override
public String toString() {
return getSpec();
}
@Override
public Ports<? extends Port> getPorts() {
return ports;
}
@Override
public String getShortName() {
if (name.length() > 3) {
return name.substring(0, 3);
} else {
return name;
}
}
/** Don't use this method. Use {@link Ports#renamePort(Port,String)}. */
protected void setName(String newName) {
this.name = newName;
}
@Override
public void addError(MetaDataError metaDataError) {
errorList.add(metaDataError);
}
@Override
public Collection<MetaDataError> getErrors() {
return Collections.unmodifiableCollection(errorList);
}
@Override
public void clear(int clearFlags) {
if ((clearFlags & CLEAR_META_DATA_ERRORS) > 0) {
this.errorList.clear();
}
if ((clearFlags & CLEAR_DATA) > 0) {
this.weakDataReference = null;
this.hardDataReference = null;
}
}
/**
* Checks whether the desired class is assignable from provided meta data object class.
*/
protected void checkDesiredClass(MetaData obj, Class<? extends MetaData> desiredClass)
throws IncompatibleMDClassException {
if (!desiredClass.isAssignableFrom(obj.getClass())) {
throw new IncompatibleMDClassException(getPorts().getOwner(), this);
}
}
@Override
public List<QuickFix> collectQuickFixes() {
List<QuickFix> fixes = new LinkedList<>();
for (MetaDataError error : getErrors()) {
fixes.addAll(error.getQuickFixes());
}
Collections.sort(fixes);
return fixes;
}
@Override
public String getSpec() {
if (getPorts() != null) {
return getPorts().getOwner().getOperator().getName() + "." + getName();
} else {
return "DUMMY." + getName();
}
}
@Override
public boolean simulatesStack() {
return simulatesStack;
}
@Override
public boolean isLocked() {
return locked;
}
@Override
public void unlock() {
this.locked = false;
}
@Override
public void lock() {
this.locked = true;
}
/** Releases of the hard reference. */
@Override
public void freeMemory() {
this.hardDataReference = null;
}
}