blob: b72802bc919340cefce372ba2fd0018a5f917e13 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016, 2017 Willink Transformations and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* E.D.Willink - Initial API and implementation
*******************************************************************************/
package org.eclipse.qvtd.runtime.internal.evaluation;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.ocl.pivot.ids.TypeId;
import org.eclipse.qvtd.runtime.evaluation.AbstractConnection;
import org.eclipse.qvtd.runtime.evaluation.AbstractTransformer;
import org.eclipse.qvtd.runtime.evaluation.Connection;
import org.eclipse.qvtd.runtime.evaluation.ExecutionVisitor;
import org.eclipse.qvtd.runtime.evaluation.Interval;
import org.eclipse.qvtd.runtime.evaluation.Invocation;
import org.eclipse.qvtd.runtime.evaluation.InvocationFailedException;
import org.eclipse.qvtd.runtime.evaluation.InvocationManager;
import org.eclipse.qvtd.runtime.evaluation.SimpleConnection;
import org.eclipse.qvtd.runtime.evaluation.SimpleIncrementalConnection;
import org.eclipse.qvtd.runtime.evaluation.SlotState;
import org.eclipse.qvtd.runtime.evaluation.StrictConnection;
import org.eclipse.qvtd.runtime.evaluation.StrictIncrementalConnection;
/**
* AbstractIntervalInternal provides the shared implementation of the intrusive blocked/waiting linked list functionality.
*/
public abstract class AbstractIntervalInternal implements Interval
{
protected final boolean debugInvocations = AbstractTransformer.INVOCATIONS.isActive();
protected final @NonNull InvocationManager invocationManager;
protected final int intervalIndex;
// public @Nullable SlotState debug_blockedBy = null;
/**
* Head of a singly linked list element of connections awaiting propagation, null when empty.
*/
private @Nullable AbstractConnection headConnection = null;
/**
* Tail of a singly linked list element of connections awaiting propagation, null when empty.
*/
private @Nullable AbstractConnection tailConnection = null;
/**
* Head of doubly linked list of blocked invocations.
*/
private @NonNull List<@NonNull Connection> connections = new ArrayList<>();
/**
* Head of doubly linked list of blocked invocations.
*/
private @Nullable AbstractInvocationInternal blockedInvocations = null;
/**
* Head of doubly linked list of unblocked invocations waiting for an execution attempt.
*/
private @Nullable AbstractInvocationInternal waitingInvocations = null;
public AbstractIntervalInternal(@NonNull InvocationManager invocationManager, int intervalIndex) {
this.invocationManager = invocationManager;
this.intervalIndex = intervalIndex;
}
@Override
public <R> R accept(@NonNull ExecutionVisitor<R> visitor) {
return visitor.visitInterval(this);
}
private synchronized void block(@NonNull Invocation invocation, @NonNull SlotState slotState) {
AbstractInvocationInternal castInvocation = (AbstractInvocationInternal) invocation;
assert castInvocation.debug_blockedBy == null;
assert castInvocation.next == castInvocation;
assert castInvocation.prev == castInvocation;
assert blockedInvocations != castInvocation;
assert waitingInvocations != castInvocation;
castInvocation.debug_blockedBy = slotState;
AbstractInvocationInternal blockedInvocations2 = blockedInvocations;
if (blockedInvocations2 == null) {
blockedInvocations = castInvocation;
}
else {
castInvocation.insertAfter(blockedInvocations2.prev);
}
slotState.block(invocation);
if (debugInvocations) {
AbstractTransformer.INVOCATIONS.println("block " + invocation + " for " + slotState);
}
}
@Override
public @NonNull Connection createConnection(@NonNull String name, @NonNull TypeId typeId, boolean isStrict) {
Connection connection;
if (isStrict) {
connection = new StrictConnection(this, name, typeId);
}
else {
connection = new SimpleConnection(this, name, typeId);
}
connections.add(connection);
return connection;
}
@Override
public Connection.@NonNull Incremental createIncrementalConnection(@NonNull String name, @NonNull TypeId typeId, boolean isStrict) {
Connection.Incremental connection;
if (isStrict) {
connection = new StrictIncrementalConnection(this, name, typeId);
}
else {
connection = new SimpleIncrementalConnection(this, name, typeId);
}
connections.add(connection);
return connection;
}
public synchronized void destroy(@NonNull Invocation invocation) {
assert invocation.getInterval() == this;
if (debugInvocations) {
AbstractTransformer.INVOCATIONS.println("destroy " + invocation);
}
AbstractInvocationInternal castInvocation = (AbstractInvocationInternal) invocation;
assert blockedInvocations != castInvocation;
if (waitingInvocations == castInvocation) {
waitingInvocations = castInvocation.next;
if (waitingInvocations == castInvocation) {
waitingInvocations = null;
}
}
AbstractInvocationInternal prevInvocation = castInvocation.prev;
if (prevInvocation != castInvocation) {
AbstractInvocationInternal nextInvocation = castInvocation.next;
prevInvocation.next = nextInvocation;
nextInvocation.prev = prevInvocation;
castInvocation.next = castInvocation;
castInvocation.prev = castInvocation;
}
}
@Override
public void diagnoseWorkLists(@NonNull StringBuilder s) {
for (AbstractInvocationInternal blocked = blockedInvocations; blocked != null; ) {
SlotState debug_blockedBy = blocked.debug_blockedBy;
s.append("\n" + intervalIndex + ": " + blocked + "\n\tblocked by " + debug_blockedBy);
if (debug_blockedBy != null) {
debug_blockedBy.debugUnblock();
}
blocked = blocked.next;
if (blocked == blockedInvocations) {
break;
}
}
for (AbstractInvocationInternal waiting = waitingInvocations; waiting != null; ) {
SlotState debug_blockedBy = waiting.debug_blockedBy;
s.append("\n" + intervalIndex + ": " + waiting + "\n\twaiting for " + debug_blockedBy);
if (debug_blockedBy != null) {
debug_blockedBy.debugUnblock();
}
waiting = waiting.next;
if (waiting == waitingInvocations) {
break;
}
}
for (AbstractConnection connection = headConnection; connection != null; connection = connection.getNextConnection()) {
s.append("\n" + intervalIndex + ": connection: " + connection);
}
}
@Override
public boolean flush() {
while (headConnection != null) {
AbstractConnection nextConnection2;
synchronized(this) {
nextConnection2 = headConnection;
if (nextConnection2 != null) {
headConnection = nextConnection2.getNextConnection();
if (headConnection == null) {
tailConnection = null;
}
nextConnection2.resetQueued();
}
}
if (nextConnection2 != null) {
nextConnection2.propagate();
}
}
while (waitingInvocations != null) {
AbstractInvocationInternal invocation = null;
synchronized (this) {
AbstractInvocationInternal waitingInvocations2 = waitingInvocations;
if (waitingInvocations2 != null) {
invocation = waitingInvocations2;
waitingInvocations = waitingInvocations2.next;
if (waitingInvocations == invocation) {
waitingInvocations = null;
}
invocation.remove();
}
}
if (invocation != null) {
if (debugInvocations) {
AbstractTransformer.INVOCATIONS.println("invoke " + invocation);
}
try {
boolean success = invocation.execute();
if (debugInvocations) {
AbstractTransformer.INVOCATIONS.println((success ? "done " : "fail ") + invocation);
}
}
catch (InvocationFailedException e) {
block(invocation, e.slotState);
}
}
}
AbstractInvocationInternal blockedInvocation = blockedInvocations;
if (blockedInvocation == null) {
return true;
}
do {
if (debugInvocations) {
AbstractTransformer.INVOCATIONS.println("still blocked " + blockedInvocation + " by " + blockedInvocation.debug_blockedBy);
}
blockedInvocation = blockedInvocation.next;
}
while (blockedInvocation != blockedInvocations);
return false;
}
@Override
public @NonNull Iterable<@NonNull Connection> getConnections() {
return connections;
}
@Override
public int getIndex() {
return intervalIndex;
}
@Override
public @NonNull InvocationManager getInvocationManager() {
return invocationManager;
}
@Override
public @NonNull String getName() {
return String.valueOf(intervalIndex);
}
@Override
public boolean isFlushed() {
if (tailConnection != null) {
return false;
}
// assert each connection fully propagatred
if (blockedInvocations != null) {
return false;
}
if (waitingInvocations != null) {
return false;
}
return true;
}
public void queue() {
invocationManager.setWorkToDoAt(intervalIndex);
}
@Override
public synchronized void queue(@NonNull Connection connection) {
AbstractConnection connection2 = (AbstractConnection)connection;
AbstractConnection tailConnection2 = tailConnection;
if (tailConnection2 == null) { // Empty list
assert headConnection == null;
assert connection2.getNextConnection() == null;
headConnection = connection2;
}
else { // New element
assert headConnection != null;
if (connection2.getNextConnection() != null) {
return;
}
tailConnection2.setNextConnection(connection2);
}
tailConnection = connection2;
queue();
}
@Override
public synchronized void queue(@NonNull Invocation invocation) {
assert invocation.getInterval() == this;
if (debugInvocations) {
AbstractTransformer.INVOCATIONS.println("queue " + invocation);
}
AbstractInvocationInternal castInvocation = (AbstractInvocationInternal) invocation;
assert castInvocation.debug_blockedBy == null;
assert blockedInvocations != castInvocation;
AbstractInvocationInternal waitingInvocations2 = waitingInvocations;
if (waitingInvocations2 == null) {
waitingInvocations = castInvocation;
}
else {
castInvocation.insertAfter(waitingInvocations2.prev);
}
queue();
}
@Override
public String toString() {
StringBuilder s = new StringBuilder();
s.append("<");
s.append(intervalIndex);
s.append("> ");
int i = 0;
for (AbstractConnection aConnection = headConnection; aConnection != null; aConnection = aConnection.getNextConnection()) {
i++;
if (i > 100) {
i = 999999;
break;
}
}
s.append(i);
s.append(" connections, ");
int j = 0;
AbstractInvocationInternal blockedInvocation = blockedInvocations;
if (blockedInvocation != null) {
j++;
while ((blockedInvocation = blockedInvocation.next) != blockedInvocations) {
if (++j > 100) {
j = 999999;
break;
}
}
}
s.append(j);
s.append(" blocked, ");
int k = 0;
AbstractInvocationInternal waitingInvocation = waitingInvocations;
if (waitingInvocation != null) {
k++;
while ((waitingInvocation = waitingInvocation.next) != waitingInvocations) {
if (++k > 100) {
k = 999999;
break;
}
}
}
s.append(k);
s.append(" waiting");
return s.toString();
}
@Override
public synchronized void unblock(@NonNull Invocation invocation) {
if (debugInvocations) {
AbstractTransformer.INVOCATIONS.println("unblock " + invocation);
}
AbstractInvocationInternal castInvocation = (AbstractInvocationInternal) invocation;
assert castInvocation.debug_blockedBy != null;
castInvocation.debug_blockedBy = null;
if (blockedInvocations == castInvocation) {
blockedInvocations = castInvocation.next;
if (blockedInvocations == castInvocation) {
blockedInvocations = null;
}
}
castInvocation.remove();
AbstractInvocationInternal waitingInvocations2 = waitingInvocations;
if (waitingInvocations2 == null) {
waitingInvocations = castInvocation;
}
else {
castInvocation.insertAfter(waitingInvocations2.prev);
}
queue();
}
}