SharedVariableManager.java
/*
** Module : SharedVariableManager.java
** Abstract : provides context-local scoped resource pools.
**
** Copyright (c) 2004-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------Description----------------------------
** 001 GES 20050729 @21924 Provides context-local scoped variable and stream pools.
** 002 GES 20050808 @22069 Moved ContextLocal inner class to a standalone class to promote
** reuse.
** 003 GES 20050818 @22163 Implemented the Scopeable interface to allow this class to be
** automatically notified of all scope start and end occurrences. This
** removes the need for client code to be instrumented to provide this
** service. Use of this class is now tied to the TransactionManager.
** 004 ECF 20050920 @22887 Implement ScopeableFactory interface. Allows generic access to
** singleton instance of this class from TransactionManager.
** 005 GES 20051004 @22930 Added support for shared buffers and error manager processing.
** 006 GES 20051101 @23163 Added support for shared frames.
** 007 GES 20060123 @24014 Converted from thread local to context local, moved initialization
** code to be based on the standard directory-based server
** initialization routines.
** 008 GES 20061010 @30262 Removed global option from frame support since this is not allowed
** in Progress.
** 009 ECF 20070328 @32652 Added shared temp-table support. Added context container for temp
** tables and add/lookup methods for temp tables.
** 010 SIY 20070711 @34430 Changed addFrame() to return existing frame if one is present in
** storage.
** 011 GES 20080822 @39520 Slight modification proposed by SVL to allow the currently
** registered global var of the same name to be returned as a result
** of the addVariable() method.
** 012 SIY 20080904 @39718 Inferred generics.
** 013 GES 20080918 @39857 Major rewrite to handle scopes differently. Now each resource
** supports some or all of the following: CURRENT, NEXT, GLOBAL. NEXT
** is the new feature that allows a resource add to wait until the next
** scope is opened before being added to the dictionary. Add'l
** signatures were added to allow a default scope that differs by
** resource type.
** 014 GES 20090422 @41914 Converted to standard string formatting.
** 015 CA 20131013 Added no-op scopeDeleted() method, required by the changes in
** Scopeable interface.
** 016 OM 20141203 Added double-initialization check for NEW SHARED GLOBAL EXTENT-s.
** 017 VIG 20141215 Added methods for shared menus support.
** 018 OM 20150903 Improved error handling in the case of missing shared temp-tables.
** 019 CA 20151027 When running an internal entry (procedure, function) of a persistent
** procedure, all its shared data (variables, tables, etc) needs to be
** pushed and popped, so that the internal entry (and any other external
** program ran from it) has access to it.
** 020 CA 20151222 Refactored registration of global shared resources: first, check
** previous scopes if a resource with the same name exists; if so, use
** that; otherwise, create a new resource and register it.
** Fixed error messages when the shared resource can no be found.
** When matching a shared resource with an existing one, check if it is
** the same (i.e. variables have the same data-type/extent/undo, buffers
** are for the same table, etc).
** 021 CA 20160420 Fixed H020 - defined global vars are checked by name in all scopes -
** if one is found (regardless if global or not), that is matched and
** returned, if matched.
** 022 CA 20160721 Shared vars are registered in the next scope, if they are not defined
** as NO-UNDO.
** CA 20160728 Refactored the shared registries; the var-related registries are
** exposed as copies for each external program, with the global scope
** shared among all programs.
** 023 CA 20160812 Performance improvements for H022: use IdentityHashMap when using
** ext prog instances as keys, var ref count is kept via a mutable
** AtomicInteger instead of a referent set.
** 024 GES 20160914 Force case-insensitive mode for the scoped dictionaries.
** 025 GES 20171220 Added TODO comment.
** 026 CA 20171202 Fixed shared variable lookup - search in the entire stack of
** procedures, not just in the context of THIS-PROCEDURE.
** Fixed a memory leak for cleaning up the shared state of a persistent
** procedure.
** CA 20171219 Temp-table, buffer and menu dictionaries must be case-sensitive, as
** the keys are java names, and not legacy names.
** CA 20171220 The BUFFER, TEMP-TABLE and STREAM resources all behave the same as
** the VARIABLE resources - the lookup is performed using the state of
** this-procedure and if not found, go back in the stack and look in
** each procedure's state.
** 027 GES 20180102 Changed a comment.
** 028 EVL 20190128 Changed the approach to keep NO-UNDO option and check the variable
** against it. Now all variables are storing in no-undo map with true
** or false value for option.
** 029 CA 20190122 Fixed shared object vars support.
** CA 20190326 Implemented ProcedureHelper and TransactionHelper instead of direct
** usage of the static API. This allows the elimination of context
** local usage in ProcedureManager and TransactionManager.
** 030 SVL 20190801 Added lookupTempTable(legacyName).
** 031 SVL 20200107 Support for shared queries.
** 032 CA 20200122 Javadoc fixes.
** 033 CA 20200930 Refactored to remove the singleton scopeable instance (which forced the scope
** notifications to use context-local state).
** CA 20201003 bufferCreator can be an IdentityHashMap, as the key is an external program
** instance.
** CA 20210508 Child shared buffers or temp-tables act like REFERENCE-ONLY, where they are bound
** to the master buffer/temp-table.
** CA 20210626 Refactored shared resource lookup - in case of non-new shared lookup, the lookup
** will always be performed on the stack. If there was a hit on the stack, then
** the initilized external program's registry will be updated with this shared
** resource.
** Also, the lookup is always done on the stack first and on the global registry
** otherwise. Previous point of keeping non-new shared resources in the registry
** ensures that the lookup will find the resource if there is a program on the stack
** referencing it.
** CA 20211222 Fixed memory leak: shared frames and menus can't exist in a persistent procedure,
** so do not process them on scope start. scopeDeleted() must ensure it is
** processing the external block of a non-persistent procedure.
** CA 20220105 Fix for CA/20211222 - scopeDeleted() must explicitly check if the procedure is
** not persistent.
** OM 20220115 APIs for queries accept a list of static BROWSE names for detecting errors.
** CA 20220206 In scopeDeleted, use the ProcedureHelper instance to avoid context-local lookup.
** CA 20220901 Refactored scope notification support: ScopeableFactory was removed, and the
** registration is now specific to each type of scopeable. For each case, the block
** will be registered for scope support (for that particular scopeable) only when
** the scopeable is 'active' (i.e. unnamed streams or accumulators are used). This
** allows a lazy registration of scopeables, to avoid the unnecessary overhead of
** processing all the scopeables for each and every block.
** CA 20221010 Performance improvements - avoid the ProcedureData lookup, by keeping a parallel
** stack of this data for THIS-PROCEDURE. Refs #6826
** CA 20220704 Unreferenced non-NEW SHARED variables are marked as such, so there will be no
** error raised if the lookup fails - this is required, as unreferenced shared
** variables need to exist on the stack, if the lookup passed. But if they are
** unreferenced, no error should be raised.
** CA 20221010 Performance improvements - avoid the ProcedureData lookup, by keeping a parallel
** stack of this data for THIS-PROCEDURE. Refs #6826
** 034 CA 20230313 Return an actual BDT instance when a shared var is never referenced in the
** current program, and also does not exist on the stack as 'new shared'.
** 035 RAA 20231009 Arrays with dynamic extent are now registered in addVariable.
** 036 CA 20230908 Fixed 'lookupVariableUnreferenced' to return an array in case of extent vars.
** 037 CA 20231026 A persistent procedure with a pending delete is no longer persistent, so
** scopeDeleted needs to be executed.
** CA 20231031 Added 'BlockDefinition' parameter to 'scopeStart'.
** 038 CA 20231129 Use method references and real singletons instead of lambdas, where possible.
** 039 EAB 20240611 Changed the parameter types used to call DmoMetadataManager.isSchemaMatch.
*/
/*
** 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 may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.util;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.atomic.*;
import java.util.function.*;
import com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.persist.orm.*;
import com.goldencode.p2j.security.*;
/**
* Provides context-local scoped variable, stream, buffer, frame and temp
* table pools.
* <p>
* All references can be added to the top scope (closest enclosing scope) or
* to the global scope. Lookups return the topmost reference by the given
* name or <code>null</code> if no reference by that name exists. The backing
* function is provided by the {@link ScopedSymbolDictionary} and this class
* exposes the necessary helpers as static methods which de-reference the
* pool that is localized to the context that is specific to the current
* authenticated user. Please see <code>SecurityManager</code> for more
* details on this facility.
* <p>
* The resource dictionaries (pools) are separate since they require
* different namespaces. Where possible, the returned reference is of
* a specific type, but at this time for all non-stream and non-temp table
* resources, only <code>Object</code> references can be returned.
* <p>
* Buffers are persistent object representations of relational database
* records.
* <p>
* Temp tables are actually buffers which point to temp tables, but this is
* simply an implementation detail. They represent shared temp table
* definitions, as opposed to shared buffers. They are managed separate from
* regular buffers to ensure a separate namespace is available.
* <p>
* Frames are object representations of a screen buffer that is specific to
* a given user-defined visual layout.
* <p>
* Implements the {@link Scopeable} interface to allow this class to be
* automatically notified of all scope start and end occurrences. This removes
* the need for client code to be instrumented to provide this service. The
* use of this class is now tied to the {@link TransactionManager} class. In
* particular, the singleton instance of this class is used in each context-
* local list of {@link Scopeable} objects as a dispatcher. This bit
* of nastiness is necessary since the Java Interface can't be implemented
* on a static basis but instead requires an instance method implementation.
* These instance methods then call the static counterparts which is where
* the content-local versions of the resource pools are accessed.
*/
public class SharedVariableManager
implements Scopeable
{
/** Context-local state related to shared variables. */
private static ContextLocal<SharedVariableManager> local = new ContextLocal<SharedVariableManager>()
{
protected SharedVariableManager initialValue()
{
return new SharedVariableManager();
};
};
/** Error used by {@link #lookupVariable}. */
private static final Consumer<String> ERROR_VAR = (vname) ->
{
String pname = ProcedureManager.getInstantiatingExternalProgram();
String msg = "%s Shared variable %s has not yet been created";
msg = String.format(msg, pname, vname);
errorHelper(392, msg);
};
/** Error used by {@link #lookupStream}. */
private static final Consumer<String> ERROR_STREAM = (vname) ->
{
String pname = ProcedureManager.getInstantiatingExternalProgram();
String msg = "%s Shared stream %s not defined yet";
msg = String.format(msg, pname, vname);
errorHelper(567, msg);
};
/** Error used by {@link #lookupQuery}. */
private static final Consumer<String> ERROR_QUERY = (vname) ->
{
errorHelper(3255, "Failure to find shared query " + vname);
};
/** Error used by {@link #lookupBuffer}. */
private static final Consumer<String> ERROR_BUFFER = (vname) ->
{
String pname = ProcedureManager.getInstantiatingExternalProgram();
String msg = "%s Unable to find shared buffer for %s";
msg = String.format(msg, pname, vname);
errorHelper(566, msg);
};
/** Error used by {@link #lookupTempTable}. */
private static final Consumer<String> ERROR_TT = (vname) ->
{
String pname = ProcedureManager.getInstantiatingExternalProgram();
String msg = "Unable to locate shared temp-table or work-table definition " +
"for table %s in procedure %s";
msg = String.format(msg, vname, pname);
errorHelper(1429, msg);
};
/** Error used by {@link #lookupFrame}. */
private static final Consumer<String> ERROR_FRAME = (vname) ->
{
String pname = ProcedureManager.getInstantiatingExternalProgram();
String msg = "%s Shared frame %s not created yet";
msg = String.format(msg, pname, vname);
errorHelper(682, msg);
};
/** The instance state. */
private final WorkArea wa = new WorkArea();
/**
* Adds a variable to the next scope that is opened.
*
* @param <T>
* Variable type.
* @param name
* The variable name.
* @param var
* The variable reference.
*
* @return The <code>var</code> instance given if it is successfully
* added, otherwise the currently registered global instance
* with the same name.
*/
public static <T> T addVariable(String name, T var)
{
return addVariable(ScopeLevel.NEXT, name, false, var, null);
}
/**
* Adds a variable to the next scope that is opened.
*
* @param <T>
* Variable type.
* @param name
* The variable name.
* @param var
* The variable reference.
* @param init
* If non null, then var is assumed to be an extent variable, and this represents
* the default value for the variable's elements.
*
* @return The <code>var</code> instance given if it is successfully
* added, otherwise the currently registered global instance
* with the same name.
*/
public static <T> T addVariable(String name, T var, Supplier<Object> init)
{
return addVariable(ScopeLevel.NEXT, name, false, var, init);
}
/**
* Adds a variable to the specified scope.
* <p>
* If the next scope is specified, the instance will be placed in the list
* of deferred instances that will be added the next time a scope is
* opened.
* <p>
* If global is specified, the instance will be added to the bottom-most
* scope on the stack.
* <p>
* If current is specified, the instance will be added to the scope that
* is on the top of the stack. If there is only one scope on the stack,
* the global and current scope will be the same scope.
* <p>
* Any attempt to add second global variable of the same name, will cause
* the passed-in instance to be ignored (the currently registered instance
* will not be replaced). In addition, the currently registered instance
* will be returned.
*
* @param <T>
* Variable type.
* @param scope
* Specifies the target scope.
* @param name
* The variable name.
* @param var
* The variable reference.
*
* @return The <code>var</code> instance given if it is successfully
* added, otherwise the currently registered global instance
* with the same name.
*/
public static <T> T addVariable(ScopeLevel scope, String name, T var)
{
return addVariable(scope, name, false, var, null);
}
/**
* Adds a variable to the next scope that is opened.
*
* @param <T>
* Variable type.
* @param noUndo
* Flag indicating if the variable was defined as NO-UNDO.
* @param name
* The variable name.
* @param var
* The variable reference.
*
* @return The <code>var</code> instance given if it is successfully
* added, otherwise the currently registered global instance
* with the same name.
*/
public static <T> T addVariable(String name, boolean noUndo, T var)
{
return addVariable(ScopeLevel.NEXT, name, noUndo, var, null);
}
/**
* Adds a variable to the next scope that is opened.
*
* @param <T>
* Variable type.
* @param name
* The variable name.
* @param noUndo
* Flag indicating if the variable was defined as NO-UNDO.
* @param var
* The variable reference.
* @param init
* If non null, then var is assumed to be an extent variable, and this represents
* the default value for the variable's elements.
*
* @return The <code>var</code> instance given if it is successfully
* added, otherwise the currently registered global instance
* with the same name.
*/
public static <T> T addVariable(String name, boolean noUndo, T var, Supplier<Object> init)
{
return addVariable(ScopeLevel.NEXT, name, noUndo, var, init);
}
/**
* Adds a variable to the specified scope.
* <p>
* If the next scope is specified, the instance will be placed in the list
* of deferred instances that will be added the next time a scope is
* opened.
* <p>
* If global is specified, the instance will be added to the bottom-most
* scope on the stack.
* <p>
* If current is specified, the instance will be added to the scope that
* is on the top of the stack. If there is only one scope on the stack,
* the global and current scope will be the same scope.
* <p>
* Any attempt to add second global variable of the same name, will cause
* the passed-in instance to be ignored (the currently registered instance
* will not be replaced). In addition, the currently registered instance
* will be returned.
*
* @param <T>
* Variable type.
* @param scope
* Specifies the target scope.
* @param name
* The variable name.
* @param noUndo
* Flag indicating if the variable was defined as NO-UNDO.
* @param var
* The variable reference.
*
* @return The <code>var</code> instance given if it is successfully
* added, otherwise the currently registered global instance
* with the same name.
*/
public static <T> T addVariable(ScopeLevel scope, String name, boolean noUndo, T var)
{
return addVariable(scope, name, noUndo, var, null);
}
/**
* Adds a variable to the specified scope.
* <p>
* If the next scope is specified, the instance will be placed in the list
* of deferred instances that will be added the next time a scope is
* opened.
* <p>
* If global is specified, the instance will be added to the bottom-most
* scope on the stack.
* <p>
* If current is specified, the instance will be added to the scope that
* is on the top of the stack. If there is only one scope on the stack,
* the global and current scope will be the same scope.
* <p>
* Any attempt to add second global variable of the same name, will cause
* the passed-in instance to be ignored (the currently registered instance
* will not be replaced). In addition, the currently registered instance
* will be returned.
*
* @param <T>
* Variable type.
* @param scope
* Specifies the target scope.
* @param name
* The variable name.
* @param var
* The variable reference.
* @param init
* If non null, then var is assumed to be an extent variable, and this represents
* the default value for the variable's elements.
*
* @return The <code>var</code> instance given if it is successfully
* added, otherwise the currently registered global instance
* with the same name.
*/
public static <T> T addVariable(ScopeLevel scope, String name, T var, Supplier<Object> init)
{
return addVariable(scope, name, false, var, init);
}
/**
* Adds a variable to the specified scope.
* <p>
* If the next scope is specified, the instance will be placed in the list
* of deferred instances that will be added the next time a scope is
* opened.
* <p>
* If global is specified, the instance will be added to the bottom-most
* scope on the stack.
* <p>
* If current is specified, the instance will be added to the scope that
* is on the top of the stack. If there is only one scope on the stack,
* the global and current scope will be the same scope.
* <p>
* Any attempt to add second global variable of the same name, will cause
* the passed-in instance to be ignored (the currently registered instance
* will not be replaced). In addition, the currently registered instance
* will be returned.
*
* @param <T>
* Variable type.
* @param scope
* Specifies the target scope.
* @param name
* The variable name.
* @param noUndo
* Flag indicating if the variable was defined as NO-UNDO.
* @param var
* The variable reference.
* @param init
* If non null, then var is assumed to be an extent variable, and this represents
* the default value for the variable's elements.
*
* @return The <code>var</code> instance given if it is successfully
* added, otherwise the currently registered global instance
* with the same name.
*/
public static <T> T addVariable(ScopeLevel scope,
String name,
boolean noUndo,
T var,
Supplier<Object> init)
{
WorkArea wa = local.get().wa;
VarRegistry vr = wa.vars.getRegistry(scope, VarRegistry::new);
SharedRegistry sr = vr.objects;
boolean extent = var.getClass().isArray();
boolean global = (scope == ScopeLevel.GLOBAL);
if (global)
{
// when searching for a match, the search is not done only in the global scope.
// a DEFINE NEW SHARED can hide a previous DEFINE NEW GLOBAL SHARED, and a subsequent
// DEFINE NEW GLOBAL SHARED will resolve to the DEFINE NEW SHARED var, not the global one
VarRegistry reg = wa.vars.lookupRegistry(name);
Object current = reg == null ? null : lookupWorker(reg.objects, name, null);
// ignore if there is a previous shared definition (global or not)
if (current != null)
{
checkSharedVariable(name, var, noUndo);
if (current instanceof object)
{
checkObjectType(name, current, var);
}
return (T) current;
}
}
addWorker(sr, scope, name, var);
// the no-undo map now keeps the option value for every variable
addWorker(vr.noUndoVars, scope, name, noUndo);
if (var instanceof object[])
{
// extent shared vars are always initialized via TypeFactory; also, no implicit
// initialization can be set for the extent vars
// save the original var, when is first defined. this will help later on, when the
// variable is reused via DEFINE NEW GLOBAL SHARED, to check if the extent matches
addWorker(vr.extentVars, scope, name, var);
}
else if (extent)
{
BaseDataType[] bdvar = (BaseDataType[]) var;
// assertion check - always pass an uninitialized array...
for (int i = 0; i < bdvar.length; i++)
{
if (bdvar[i] != null)
{
throw new IllegalArgumentException(
"The array index " + i + " is already set for shared extsent var " + name + "!");
}
}
// save the original var, when is first defined. this will help later on, when the
// variable is reused via DEFINE NEW GLOBAL SHARED, to check if the extent matches
addWorker(vr.extentVars, scope, name, var);
Object vinit = (init == null ? null : init.get());
if (vinit == null)
{
BaseDataType.initializeDefaultExtent(bdvar);
}
else
{
if (vinit instanceof BaseDataType[])
{
BaseDataType[] bdinit = (BaseDataType[]) vinit;
if (bdvar.length == 0)
{
// resize it
bdvar = ArrayAssigner.resize(bdvar, bdinit.length);
var = (T) bdvar;
}
if (bdinit.length == bdvar.length)
{
ArrayAssigner.assignMulti(bdvar, bdinit);
}
else if (bdinit.length < bdvar.length)
{
for (int i = 0; i < bdinit.length; i++)
{
if (bdvar[i] == null)
{
bdvar[i] = bdinit[i].duplicate();
}
else
{
bdvar[i].assign(bdinit[i]);
}
}
if (bdinit.length > 0)
{
ArrayAssigner.assignMulti(bdvar, bdinit.length, bdinit[bdinit.length - 1]);
}
}
}
else if (init instanceof BaseDataType)
{
if (bdvar.length == 0)
{
// resize it
bdvar = ArrayAssigner.resize(bdvar, 1);
var = (T) bdvar;
}
ArrayAssigner.assignMulti((BaseDataType[]) var, (BaseDataType) init);
}
else
{
throw new RuntimeException(
"Extent GLOBAL SHARED initializer must be a BaseDataType - received a " +
init.getClass()
);
}
}
}
if (!noUndo)
{
if (extent)
{
TransactionManager.registerAt(scope, (LazyUndoable[]) var);
}
else
{
TransactionManager.registerAt(scope, (LazyUndoable) var);
}
}
if (extent && Array.getLength(var) == 0)
{
ArrayAssigner.registerDynamicArray((BaseDataType[]) var);
}
return var;
}
/**
* Searches all scopes from the top of the stack down for the first variable with the given
* name.
*
* @param <T>
* Variable type.
* @param name
* The variable name.
* @param expected
* The defined type of the slave variable (must match the master var type).
*
* @return The variable reference associated with the name or <code>null</code> if not found.
*
* @throws ErrorConditionException
* When the variable cannot be found or it does not match the master.
*/
public static <T> T lookupVariable(String name, Class<? extends BaseDataType> expected)
throws ErrorConditionException
{
return lookupVariable(name, expected, null, null, false);
}
/**
* Searches all scopes from the top of the stack down for the first variable with the given
* name.
*
* @param <T>
* Variable type.
* @param name
* The variable name.
* @param expected
* The defined type of the slave variable (must match the master var type).
* @param type
* In case of {@link object} variables, this var's underlying type.
*
* @return The variable reference associated with the name or <code>null</code> if not found.
*
* @throws ErrorConditionException
* When the variable cannot be found or it does not match the master.
*/
public static <T> T lookupVariable(String name,
Class<? extends BaseDataType> expected,
Class<? extends _BaseObject_> type)
throws ErrorConditionException
{
return lookupVariable(name, expected, type, null, false);
}
/**
* Searches all scopes from the top of the stack down for the first variable with the given
* name.
*
* @param <T>
* Variable type.
* @param name
* The variable name.
* @param expected
* The defined type of the slave variable (must match the master var type).
* @param noUndo
* Flag indicating if the variable was defined as NO-UNDO.
*
* @return The variable reference associated with the name or <code>null</code> if not found.
*
* @throws ErrorConditionException
* When the variable cannot be found or it does not match the master.
*/
public static <T> T lookupVariable(String name,
Class<? extends BaseDataType> expected,
boolean noUndo)
throws ErrorConditionException
{
return lookupVariable(name, expected, null, null, noUndo);
}
/**
* Searches all scopes from the top of the stack down for the first variable with the given
* name.
*
* @param <T>
* Variable type.
* @param name
* The variable name.
* @param expected
* The defined type of the slave variable (must match the master var type).
* @param type
* In case of {@link object} variables, this var's underlying type.
* @param noUndo
* Flag indicating if the variable was defined as NO-UNDO.
*
* @return The variable reference associated with the name or <code>null</code> if not found.
*
* @throws ErrorConditionException
* When the variable cannot be found or it does not match the master.
*/
public static <T> T lookupVariable(String name,
Class<? extends BaseDataType> expected,
Class<? extends _BaseObject_> type,
boolean noUndo)
throws ErrorConditionException
{
return lookupVariable(name, expected, type, null, noUndo);
}
/**
* Searches all scopes from the top of the stack down for the first variable with the given
* name.
*
* @param <T>
* Variable type.
* @param name
* The variable name.
* @param expected
* The defined type of the slave variable (must match the master var type).
* @param extent
* The found var must be an extent var of the specified extent.
*
* @return The variable reference associated with the name or <code>null</code> if not found.
*
* @throws ErrorConditionException
* When the variable cannot be found or it does not match the master.
*/
public static <T> T lookupVariable(String name,
Class<? extends BaseDataType> expected,
Integer extent)
throws ErrorConditionException
{
return lookupVariable(name, expected, extent, false);
}
/**
* Searches all scopes from the top of the stack down for the first variable with the given
* name.
*
* @param <T>
* Variable type.
* @param name
* The variable name.
* @param expected
* The defined type of the slave variable (must match the master var type).
* @param type
* In case of {@link object} variables, this var's underlying type.
* @param extent
* The found var must be an extent var of the specified extent.
*
* @return The variable reference associated with the name or <code>null</code> if not found.
*
* @throws ErrorConditionException
* When the variable cannot be found or it does not match the master.
*/
public static <T> T lookupVariable(String name,
Class<? extends BaseDataType> expected,
Class<? extends _BaseObject_> type,
Integer extent)
throws ErrorConditionException
{
return lookupVariable(name, expected, type, extent, false);
}
/**
* Searches all scopes from the top of the stack down for the first variable with the given
* name.
*
* @param <T>
* Variable type.
* @param name
* The variable name.
* @param expected
* The defined type of the slave variable (must match the master var type).
* @param extent
* The found var must be an extent var of the specified extent.
* @param noUndo
* Flag indicating if the variable was defined as NO-UNDO.
*
* @return The variable reference associated with the name or <code>null</code> if not found.
*
* @throws ErrorConditionException
* When the variable cannot be found or it does not match the master.
*/
public static <T> T lookupVariable(String name,
Class<? extends BaseDataType> expected,
Integer extent,
boolean noUndo)
throws ErrorConditionException
{
return lookupVariable(name, expected, null, extent, noUndo);
}
/**
* Searches all scopes from the top of the stack down for the first variable with the given
* name.
*
* @param <T>
* Variable type.
* @param name
* The variable name.
* @param expected
* The defined type of the slave variable (must match the master var type).
* @param type
* In case of {@link object} variables, this var's underlying type.
* @param extent
* The found var must be an extent var of the specified extent.
* @param noUndo
* Flag indicating if the variable was defined as NO-UNDO.
*
* @return The variable reference associated with the name or <code>null</code> if not found.
*
* @throws ErrorConditionException
* When the variable cannot be found or it does not match the master.
*/
public static <T> T lookupVariable(String name,
Class<? extends BaseDataType> expected,
Class<? extends _BaseObject_> type,
Integer extent,
boolean noUndo)
throws ErrorConditionException
{
WorkArea wa = local.get().wa;
Object found = lookupWorker(wa.vars, name, ERROR_VAR);
if (found == null)
{
// fail early if it is not found
return null;
}
Object var = (extent != null ? Array.newInstance(expected, Math.max(0, extent))
: type != null ? new object(type)
: BaseDataType.generateDefault(expected));
checkSharedVariable(name, var, noUndo);
if (type != null)
{
checkObjectType(name, (object) found, var);
}
VarRegistry vr = wa.vars.getRegistry(ScopeLevel.NEXT, VarRegistry::new);
SharedRegistry sr = vr.objects;
addWorker(sr, ScopeLevel.NEXT, name, found);
addWorker(vr.noUndoVars, ScopeLevel.NEXT, name, noUndo);
if (found.getClass().isArray())
{
addWorker(vr.extentVars, ScopeLevel.NEXT, name, found);
}
return (T) found;
}
/**
* Searches all scopes from the top of the stack down for the first variable with the given
* name.
* <p>
* An unreferenced variable will not raise an ERROR condition, if the lookup fails - but otherwise, if the
* lookup passed, the shared variable must exist on the stack.
*
* @param <T>
* Variable type.
* @param name
* The variable name.
* @param expected
* The defined type of the slave variable (must match the master var type).
*
* @return The variable reference associated with the name or <code>null</code> if not found.
*/
public static <T> T lookupVariableUnreferenced(String name, Class<? extends BaseDataType> expected)
{
return lookupVariableUnreferenced(name, expected, null, null, false);
}
/**
* Searches all scopes from the top of the stack down for the first variable with the given
* name.
* <p>
* An unreferenced variable will not raise an ERROR condition, if the lookup fails - but otherwise, if the
* lookup passed, the shared variable must exist on the stack.
*
* @param <T>
* Variable type.
* @param name
* The variable name.
* @param expected
* The defined type of the slave variable (must match the master var type).
* @param type
* In case of {@link object} variables, this var's underlying type.
*
* @return The variable reference associated with the name or <code>null</code> if not found.
*/
public static <T> T lookupVariableUnreferenced(String name,
Class<? extends BaseDataType> expected,
Class<? extends _BaseObject_> type)
{
return lookupVariableUnreferenced(name, expected, type, null, false);
}
/**
* Searches all scopes from the top of the stack down for the first variable with the given
* name.
* <p>
* An unreferenced variable will not raise an ERROR condition, if the lookup fails - but otherwise, if the
* lookup passed, the shared variable must exist on the stack.
*
* @param <T>
* Variable type.
* @param name
* The variable name.
* @param expected
* The defined type of the slave variable (must match the master var type).
* @param noUndo
* Flag indicating if the variable was defined as NO-UNDO.
*
* @return The variable reference associated with the name or <code>null</code> if not found.
*/
public static <T> T lookupVariableUnreferenced(String name,
Class<? extends BaseDataType> expected,
boolean noUndo)
{
return lookupVariableUnreferenced(name, expected, null, null, noUndo);
}
/**
* Searches all scopes from the top of the stack down for the first variable with the given
* name.
* <p>
* An unreferenced variable will not raise an ERROR condition, if the lookup fails - but otherwise, if the
* lookup passed, the shared variable must exist on the stack.
*
* @param <T>
* Variable type.
* @param name
* The variable name.
* @param expected
* The defined type of the slave variable (must match the master var type).
* @param type
* In case of {@link object} variables, this var's underlying type.
* @param noUndo
* Flag indicating if the variable was defined as NO-UNDO.
*
* @return The variable reference associated with the name or <code>null</code> if not found.
*/
public static <T> T lookupVariableUnreferenced(String name,
Class<? extends BaseDataType> expected,
Class<? extends _BaseObject_> type,
boolean noUndo)
{
return lookupVariableUnreferenced(name, expected, type, null, noUndo);
}
/**
* Searches all scopes from the top of the stack down for the first variable with the given
* name.
* <p>
* An unreferenced variable will not raise an ERROR condition, if the lookup fails - but otherwise, if the
* lookup passed, the shared variable must exist on the stack.
*
* @param <T>
* Variable type.
* @param name
* The variable name.
* @param expected
* The defined type of the slave variable (must match the master var type).
* @param extent
* The found var must be an extent var of the specified extent.
*
* @return The variable reference associated with the name or <code>null</code> if not found.
*/
public static <T> T lookupVariableUnreferenced(String name,
Class<? extends BaseDataType> expected,
Integer extent)
{
return lookupVariableUnreferenced(name, expected, extent, false);
}
/**
* Searches all scopes from the top of the stack down for the first variable with the given
* name.
* <p>
* An unreferenced variable will not raise an ERROR condition, if the lookup fails - but otherwise, if the
* lookup passed, the shared variable must exist on the stack.
*
* @param <T>
* Variable type.
* @param name
* The variable name.
* @param expected
* The defined type of the slave variable (must match the master var type).
* @param type
* In case of {@link object} variables, this var's underlying type.
* @param extent
* The found var must be an extent var of the specified extent.
*
* @return The variable reference associated with the name or <code>null</code> if not found.
*/
public static <T> T lookupVariableUnreferenced(String name,
Class<? extends BaseDataType> expected,
Class<? extends _BaseObject_> type,
Integer extent)
{
return lookupVariableUnreferenced(name, expected, type, extent, false);
}
/**
* Searches all scopes from the top of the stack down for the first variable with the given
* name.
* <p>
* An unreferenced variable will not raise an ERROR condition, if the lookup fails - but otherwise, if the
* lookup passed, the shared variable must exist on the stack.
*
* @param <T>
* Variable type.
* @param name
* The variable name.
* @param expected
* The defined type of the slave variable (must match the master var type).
* @param extent
* The found var must be an extent var of the specified extent.
* @param noUndo
* Flag indicating if the variable was defined as NO-UNDO.
*
* @return The variable reference associated with the name or <code>null</code> if not found.
*/
public static <T> T lookupVariableUnreferenced(String name,
Class<? extends BaseDataType> expected,
Integer extent,
boolean noUndo)
{
return lookupVariableUnreferenced(name, expected, null, extent, noUndo);
}
/**
* Searches all scopes from the top of the stack down for the first variable with the given
* name.
* <p>
* An unreferenced variable will not raise an ERROR condition, if the lookup fails - but otherwise, if the
* lookup passed, the shared variable must exist on the stack.
*
* @param <T>
* Variable type.
* @param name
* The variable name.
* @param expected
* The defined type of the slave variable (must match the master var type).
* @param type
* In case of {@link object} variables, this var's underlying type.
* @param extent
* The found var must be an extent var of the specified extent.
* @param noUndo
* Flag indicating if the variable was defined as NO-UNDO.
*
* @return The variable reference associated with the name or <code>null</code> if not found.
*/
public static <T> T lookupVariableUnreferenced(String name,
Class<? extends BaseDataType> expected,
Class<? extends _BaseObject_> type,
Integer extent,
boolean noUndo)
{
Object[] res = new Object[1];
ErrorManager.nestedSilent(() ->
{
try
{
res[0] = lookupVariable(name, expected, type, extent, noUndo);
}
catch (ErrorConditionException err)
{
if (extent != null)
{
res[0] = Array.newInstance(expected, Math.max(0, extent));
}
else
{
res[0] = BaseDataType.generateDefault(expected);
}
throw err;
}
});
return (T) res[0];
}
/**
* Adds a stream to the {@link ScopeLevel#CURRENT} scope.
*
* @param name
* The stream name.
* @param stream
* The stream reference.
*/
public static void addStream(String name, Stream stream)
{
addStream(ScopeLevel.CURRENT, name, stream);
}
/**
* Adds a stream to the specified scope.
* <p>
* If the next scope is specified, the instance will be placed in the list
* of deferred instances that will be added the next time a scope is opened.
* <p>
* If global is specified, the instance will be added to the bottom-most
* scope on the stack.
* <p>
* If current is specified, the instance will be added to the scope that
* is on the top of the stack. If there is only one scope on the stack,
* the global and current scope will be the same scope.
*
* @param scope
* Specifies the target scope.
* @param name
* The stream name.
* @param stream
* The stream reference.
*/
public static void addStream(ScopeLevel scope, String name, Stream stream)
{
WorkArea wa = local.get().wa;
PersistentRegistry vr = wa.streams.getRegistry(scope, PersistentRegistry::new);
addWorker(vr.objects, scope, name, stream);
// as this is not reached via the scopeStart, we do it here (but only for NEW streams)
AtomicInteger count = (AtomicInteger) wa.streams.refs.get(stream);
if (count == null)
{
wa.streams.refs.put(stream, count = new AtomicInteger(1));
}
else
{
count.incrementAndGet();
}
}
/**
* Searches all scopes from the top of the stack down for the first
* stream with the given name.
*
* @param name
* The stream name.
*
* @return The stream reference associated with the name or
* <code>null</code> if not found.
*
* @throws ErrorConditionException
* When the stream cannot be found and silent error mode is
* disabled.
*/
public static Stream lookupStream(String name)
throws ErrorConditionException
{
WorkArea wa = local.get().wa;
Stream found = (Stream) lookupWorker(wa.streams, name, ERROR_STREAM);
addStream(ScopeLevel.NEXT, name, found);
return found;
}
/**
* Adds a buffer to the {@link ScopeLevel#CURRENT} scope.
*
* @param name
* The buffer name.
* @param buffer
* The buffer reference.
*/
public static <T> void addBuffer(String name, T buffer)
{
addBuffer(ScopeLevel.CURRENT, name, buffer);
}
/**
* Creates and adds a query to the {@link ScopeLevel#NEXT} scope.
*
* @param name
* The query name.
* @param scrolling
* Defines if the query should be scrolling.
*
* @deprecated Use the overloaded method with lookupQuery(String, boolean, String...) signature instead.
*
* @return The {@code QueryWrapper} created using the current arguments.
*/
@Deprecated
public static QueryWrapper addQuery(String name, boolean scrolling)
{
return addQuery(ScopeLevel.NEXT, name, scrolling);
}
/**
* Creates and adds a query to the {@link ScopeLevel#NEXT} scope.
*
* @param name
* The query name.
* @param scrolling
* Defines if the query should be scrolling.
* @param browseNames
* The legacy name of any BROWSEs widgets which use this query.
*
* @return The {@code QueryWrapper} created using the current arguments.
*/
public static QueryWrapper addQuery(String name, boolean scrolling, String... browseNames)
{
return addQuery(ScopeLevel.NEXT, name, scrolling, browseNames);
}
/**
* Creates and adds a query to the specified scope.
* <p>
* If the next scope is specified, the instance will be placed in the list
* of deferred instances that will be added the next time a scope is opened.
* <p>
* If global is specified, the instance will be added to the bottom-most
* scope on the stack.
* <p>
* If current is specified, the instance will be added to the scope that
* is on the top of the stack. If there is only one scope on the stack,
* the global and current scope will be the same scope.
*
* @param scope
* Specifies the target scope.
* @param name
* The query name.
* @param scrolling
* Defines if the query should be scrolling.
* @param brwNames
* The legacy name of any BROWSEs widgets which use this query.
*
* @return The {@code QueryWrapper} created using the current arguments.
*/
public static QueryWrapper addQuery(ScopeLevel scope, String name, boolean scrolling, String... brwNames)
{
QueryWrapper query = new QueryWrapper(name, scrolling, brwNames);
addQuery(scope, name, query);
return query;
}
/**
* Creates and adds a query to the specified scope.
* <p>
* If the next scope is specified, the instance will be placed in the list
* of deferred instances that will be added the next time a scope is opened.
* <p>
* If global is specified, the instance will be added to the bottom-most
* scope on the stack.
* <p>
* If current is specified, the instance will be added to the scope that
* is on the top of the stack. If there is only one scope on the stack,
* the global and current scope will be the same scope.
*
* @param scope
* Specifies the target scope.
* @param name
* The query name.
* @param query
* The query instance
*/
public static void addQuery(ScopeLevel scope, String name, QueryWrapper query)
{
WorkArea wa = local.get().wa;
PersistentRegistry vr = wa.queries.getRegistry(scope, PersistentRegistry::new);
addWorker(vr.objects, scope, name, query);
AtomicInteger count = wa.queries.refs.get(query);
if (count == null)
{
wa.queries.refs.put(query, new AtomicInteger(1));
}
else
{
count.incrementAndGet();
}
}
/**
* Searches all scopes from the top of the stack down for the first query with the given name.
*
* @param name
* The query name.
*
* @return The query reference associated with the name or {@code null} if not found.
*
* @throws ErrorConditionException
* When the query cannot be found.
*
* @deprecated Use the overloaded method with lookupQuery(String, String...) signature instead.
*/
@Deprecated
public static QueryWrapper lookupQuery(String name)
throws ErrorConditionException
{
return lookupQuery(name, new String[] {});
}
/**
* Searches all scopes from the top of the stack down for the first query with the given name.
*
* @param name
* The query name.
*
* @return The query reference associated with the name or {@code null} if not found.
*
* @throws ErrorConditionException
* When the query cannot be found.
*/
public static QueryWrapper lookupQuery(String name, String... browses)
throws ErrorConditionException
{
WorkArea wa = local.get().wa;
QueryWrapper found = (QueryWrapper) lookupWorker(wa.queries, name, ERROR_QUERY);
// TODO: shared queries can be defined in internal procedures... ???
addQuery(ScopeLevel.NEXT, name, found);
return found;
}
/**
* Adds a buffer to the specified scope.
* <p>
* If the next scope is specified, the instance will be placed in the list
* of deferred instances that will be added the next time a scope is opened.
* <p>
* If global is specified, the instance will be added to the bottom-most
* scope on the stack.
* <p>
* If current is specified, the instance will be added to the scope that
* is on the top of the stack. If there is only one scope on the stack,
* the global and current scope will be the same scope.
*
* @param <T>
* Buffer type.
* @param scope
* Specifies the target scope.
* @param name
* The buffer name.
* @param buffer
* The buffer reference.
*/
public static <T> void addBuffer(ScopeLevel scope, String name, T buffer)
{
WorkArea wa = local.get().wa;
PersistentRegistry vr = wa.buffers.getRegistry(scope, BufferRegistry::new);
addWorker(vr.objects, scope, name, buffer);
// as this is not reached via the scopeStart, we do it here (but only for NEW buffers)
wa.bufferCreator.put(buffer, wa.pm._thisProcedure());
AtomicInteger count = (AtomicInteger) wa.buffers.refs.get(buffer);
if (count == null)
{
wa.buffers.refs.put(buffer, count = new AtomicInteger(1));
}
else
{
count.incrementAndGet();
}
}
/**
* Searches all scopes from the top of the stack down for the first buffer with the given name.
*
* @param <T>
* Buffer type.
* @param name
* The buffer name.
* @param ttname
* The temp-table name.
* @param expected
* The expected DMO type of the resolved buffer.
*
* @return The buffer reference associated with the name or
* <code>null</code> if not found.
*
* @throws ErrorConditionException
* When the buffer cannot be found or it does not match the master.
*/
public static <T> T lookupBuffer(String name, String ttname, Class<?> expected)
throws ErrorConditionException
{
WorkArea wa = local.get().wa;
T found = (T) lookupWorker(wa.buffers, name, ERROR_BUFFER);
if (found == null)
{
// fail early if it is not found
return null;
}
boolean isTemp = found instanceof Temporary;
RecordBuffer childBuffer = (RecordBuffer) ((BufferReference) found).buffer();
Class<? extends DataModelObject> foundDmo = childBuffer.getDMOInterface();
BooleanSupplier pred = () ->
{
if (isTemp && !Temporary.class.isAssignableFrom(expected))
{
return false;
}
return DmoMetadataManager.isSchemaMatch(DmoMetadataManager.getDmoIface(expected), foundDmo);
};
Runnable error = () ->
{
String msg = "Buffer %s for %s in %s does not match shared buffer defined NEW in %s";
String pname = ProcedureManager.getInstantiatingExternalProgram();
String tpname = "n/a";
Object creator = wa.bufferCreator.get(found);
if (creator != null)
{
// find the external program where the temp-table was originally defined...
tpname = local.get().wa.pm.getRelativeName(creator);
}
msg = String.format(msg, name, ttname, pname, tpname);
errorHelper(1763, msg);
};
if (!checkSharedResource(pred, error))
{
throw new IllegalStateException(
"An exception should have been thrown by checkedSharedResource!");
}
T buffer = found;
if (isTemp)
{
buffer = (T) TemporaryBuffer.useShared((Temporary) found, expected, name);
}
else
{
// we can safely return the master buffer if they are for a permanent table
}
addBuffer(ScopeLevel.NEXT, name, buffer);
return found;
}
/**
* Creates (via {@link TemporaryBuffer#define} and adds a temporary table buffer to the next
* scope - the instance will be placed in the list of deferred instances that will be added
* the next time a scope is opened.
*
* @param <T>
* Type of constructed instance.
* @param name
* The temporary table buffer name.
* @param dmoBufIface
* Interface which defines DMO's public API. This must be a sub-interface of
* {@link Temporary}.
* @param variable
* Name of the variable with which the record buffer will be associated in the
* transaction manager.
* @param legacyName
* Legacy name of the temp-table.
* @param undoable
* <code>true</code> if changes made to records managed by this buffer can be undone;
* <code>false</code> if such changes are committed immediately, such that they cannot
* be undone.
*
* @return An object which implements the specified interface, but which initially has no
* backing data record.
*/
public static <T extends TempTableBuffer> T addTempTable(String name,
Class<T> dmoBufIface,
String variable,
String legacyName,
boolean undoable)
{
return addTempTable(ScopeLevel.NEXT, name, dmoBufIface, variable, legacyName, undoable);
}
/**
* Creates (via {@link TemporaryBuffer#define} and adds a temporary table buffer to the next
* scope - the instance will be placed in the list of deferred instances that will be added
* the next time a scope is opened.
*
* @param <T>
* Type of constructed instance.
* @param name
* The temporary table buffer name.
* @param dmoBufIface
* Interface which defines DMO's public API. This must be a sub-interface of
* {@link Temporary}.
* @param variable
* Name of the variable with which the record buffer will be associated in the
* transaction manager.
* @param legacyName
* Legacy name of the temp-table.
*
* @return An object which implements the specified interface, but which initially has no
* backing data record.
*/
public static <T extends TempTableBuffer> T addTempTable(String name,
Class<T> dmoBufIface,
String variable,
String legacyName)
{
return addTempTable(ScopeLevel.NEXT, name, dmoBufIface, variable, legacyName, true);
}
/**
* Creates (via {@link TemporaryBuffer#define} and adds a temporary table buffer to the
* specified scope.
* <p>
* If the next scope is specified, the instance will be placed in the list of deferred
* instances that will be added the next time a scope is opened.
* <p>
* If global is specified, the instance will be added to the bottom-most scope on the stack.
* Also, first, existing temp-tables with the same name are searched - if one is found in one
* of the previous scopes, then that temp-table is returned only if its DMO interface is the
* same as or it implements the specified <code>dmoBufIface</code>
* <p>
* If current is specified, the instance will be added to the scope that is on the top of the
* stack. If there is only one scope on the stack, the global and current scope will be the
* same scope.
*
* @param <T>
* Type of constructed instance.
* @param scope
* Specifies the target scope.
* @param name
* The temporary table buffer name.
* @param dmoBufIface
* Interface which defines DMO's public API. This must be a sub-interface of
* {@link Temporary}.
* @param variable
* Name of the variable with which the record buffer will be associated in the
* transaction manager.
* @param legacyName
* Legacy name of the temp-table.
*
* @return An object which implements the specified interface, but which initially has no
* backing data record.
*/
public static <T extends TempTableBuffer> T addTempTable(ScopeLevel scope,
String name,
Class<T> dmoBufIface,
String variable,
String legacyName)
{
return addTempTable(scope, name, dmoBufIface, variable, legacyName, true);
}
/**
* Creates (via {@link TemporaryBuffer#define} and adds a temporary table buffer to the
* specified scope.
* <p>
* If the next scope is specified, the instance will be placed in the list of deferred
* instances that will be added the next time a scope is opened.
* <p>
* If global is specified, the instance will be added to the bottom-most scope on the stack.
* Also, first, existing temp-tables with the same name are searched - if one is found in one
* of the previous scopes, then that temp-table is returned only if its DMO interface is the
* same as or it implements the specified <code>dmoBufIface</code>
* <p>
* If current is specified, the instance will be added to the scope that is on the top of the
* stack. If there is only one scope on the stack, the global and current scope will be the
* same scope.
*
* @param <T>
* Type of constructed instance.
* @param scope
* Specifies the target scope.
* @param name
* The temporary table buffer name.
* @param dmoBufIface
* Interface which defines DMO's public API. This must be a sub-interface of
* {@link Temporary}.
* @param variable
* Name of the variable with which the record buffer will be associated in the
* transaction manager.
* @param legacyName
* Legacy name of the temp-table.
* @param undoable
* <code>true</code> if changes made to records managed by this buffer can be undone;
* <code>false</code> if such changes are committed immediately, such that they cannot
* be undone.
*
* @return An object which implements the specified interface, but which initially has no
* backing data record.
*/
public static <T extends TempTableBuffer> T addTempTable(ScopeLevel scope,
String name,
Class<T> dmoBufIface,
String variable,
String legacyName,
boolean undoable)
{
WorkArea wa = local.get().wa;
PersistentRegistry vr = wa.tempTables.getRegistry(scope, PersistentRegistry::new);
SharedRegistry sr = vr.objects;
boolean global = (scope == ScopeLevel.GLOBAL);
T var = null;
if (global)
{
Object found = sr.shared.lookupSymbol(name);
// ignore repeated global definitions
if (found != null)
{
checkSharedTempTable(found, name, dmoBufIface);
// create a new buffer for this request (subsequent calls which re-use the temp-table
// will create a separate slave buffer - do not reuse the master buffer)
var = TemporaryBuffer.define((T) found, variable, legacyName, false);
}
}
if (var == null)
{
var = TemporaryBuffer.define(dmoBufIface, variable, legacyName, global, undoable);
}
addWorker(sr, scope, name, var);
PersistentRegistry ttNamesReg = wa.tempTableNames.getRegistry(scope, PersistentRegistry::new);
addWorker(ttNamesReg.objects, scope, legacyName, name);
return var;
}
/**
* Searches all scopes from the top of the stack down for the first temp
* table with the given name.
*
* @param <T>
* Variable type.
* @param name
* The temp table buffer name.
* @param expected
* The expected DMO type of the resolved temp-table.
*
* @return The temp table buffer reference associated with the name or
* <code>null</code> if not found.
*
* @throws ErrorConditionException
* When the temp-table cannot be found or it does not match the master.
*/
public static <T extends Temporary> T lookupTempTable(String name, Class<T> expected)
throws ErrorConditionException
{
return lookupTempTable(name, expected, ERROR_TT);
}
/**
* Searches all scopes from the top of the stack down for the first temp
* table with the given legacy name.
*
* @param legacyName
* Legacy name of the target temp table.
*
* @return The temp table buffer reference associated with the name or
* <code>null</code> if not found.
*/
public static <T extends Temporary> T lookupTempTable(String legacyName)
throws ErrorConditionException
{
String variableName = (String) lookupWorker(local.get().wa.tempTableNames, legacyName, null);
return lookupTempTable(variableName, null, null);
}
/**
* Adds a frame to the {@link ScopeLevel#NEXT} scope.
* <p>
* The instance will be placed in the list of deferred instances that will
* be added the next time a scope is opened.
*
* @param name
* The frame name.
* @param frame
* The frame reference.
*
* @return Previous instance with the same name or <code>null</code> if
* there were no such instance. If there is a previous instance,
* it will be replaced with this new instance.
*/
public static <T> T addFrame(String name, T frame)
{
return addFrame(ScopeLevel.NEXT, name, frame);
}
/**
* Adds a frame to the specified scope.
* <p>
* If the next scope is specified, the instance will be placed in the list
* of deferred instances that will be added the next time a scope is
* opened.
* <p>
* If global is specified, the instance will be added to the next
* scope on the stack, as there is no such thing as a global frame in
* the Progress 4GL.
* <p>
* If current is specified, the instance will be added to the scope that
* is on the top of the stack. If there is only one scope on the stack,
* the global and current scope will be the same scope.
*
* @param <T>
* Frame type.
* @param scope
* Specifies the target scope.
* @param name
* The frame name.
* @param frame
* The frame reference.
*
* @return Previous instance with the same name or <code>null</code> if
* there were no such instance. If there is a previous instance,
* it will be replaced with this new instance.
*/
public static <T> T addFrame(ScopeLevel scope, String name, T frame)
{
if (scope == ScopeLevel.GLOBAL)
{
scope = ScopeLevel.NEXT;
}
WorkArea wa = local.get().wa;
SharedRegistry sr = wa.frames;
T result = (T) sr.shared.lookupSymbol(name);
addWorker(sr, scope, name, frame);
return result;
}
/**
* Adds a menu definition to the {@link ScopeLevel#NEXT} scope.
* <p>
* The instance will be placed in the list of deferred instances that will
* be added the next time a scope is opened.
*
* @param name
* The menu name.
* @param menu
* The menu definition reference.
*/
public static <T> void addMenu(String name, T menu)
{
addMenu(ScopeLevel.NEXT, name, menu);
}
/**
* Adds a menu to the specified scope.
* <p>
* If the next scope is specified, the instance will be placed in the list
* of deferred instances that will be added the next time a scope is
* opened.
* <p>
* If global is specified, the instance will be added to the next
* scope on the stack, as GLOBAL is not a valid option for shared menus.
* <p>
* If current is specified, the instance will be added to the scope that
* is on the top of the stack. If there is only one scope on the stack,
* the global and current scope will be the same scope.
*
* @param <T>
* Menu definition type.
* @param scope
* Specifies the target scope.
* @param name
* The menu name.
* @param menu
* The menu reference.
*/
public static <T> void addMenu(ScopeLevel scope, String name, T menu)
{
if (scope == ScopeLevel.GLOBAL)
{
scope = ScopeLevel.NEXT;
}
WorkArea wa = local.get().wa;
addWorker(wa.menus, scope, name, menu);
}
/**
* Searches all scopes from the top of the stack down for the first frame with the given name.
*
* @param <T>
* Frame type.
* @param name
* The frame name.
*
* @return The frame reference associated with the name or <code>null</code> if not found.
*
* @throws ErrorConditionException
* When the frame cannot be found and silent error mode is disabled.
*/
public static <T> T lookupFrame(String name)
throws ErrorConditionException
{
WorkArea wa = local.get().wa;
return (T) lookupWorker(wa.frames, name, ERROR_FRAME);
}
/**
* Searches all scopes from the top of the stack down for the first menu with the given name.
*
* @param <T>
* menu definition type.
* @param name
* The menu name.
*
* @return The menu definition reference associated with the name or <code>null</code> if not found.
*
* @throws ErrorConditionException
* When the menu cannot be found and silent error mode is disabled.
*/
public static <T> T lookupMenu(String name)
throws ErrorConditionException
{
WorkArea wa = local.get().wa;
return (T) lookupWorker(wa.menus, name, null);
}
/**
* Provides the singleton instance of this class as a {@link Scopeable}.
*
* @return The only instance of this class.
*/
public Scopeable createScopeable()
{
return this;
}
/**
* Provides a notification that a new scope is about to be entered.
*
* @param block
* The explicit block definition which required this notification.
*/
@Override
public void scopeStart(BlockDefinition block)
{
// shared resources can be defined only at the external program
if (!block.external || wa.tm.isGlobalBlock())
{
return;
}
Object thisProc = wa.pm._thisProcedure();
Consumer<SharedRegistry> c = (sr) ->
{
sr.shared.addScope(null);
if (sr.pending != null)
{
Set<String> keys = sr.pending.keySet();
for (String next : keys)
{
Object resource = sr.pending.get(next);
sr.shared.addSymbol(false, next, resource);
}
sr.pending = null;
}
};
if (!wa.pm.isThisProcedurePersistent())
{
// menu and frames can't be in a persistent procedure
for (SharedRegistry sr : wa.resources)
{
c.accept(sr);
}
}
for (PersistentShares pshares : wa.persistentResources)
{
PersistentRegistry pending = pshares.pending;
if (pending == null)
{
// nothing registered in the current ran program
continue;
}
pshares.pending = null;
// save the registry at THIS-PROCEDURE
pshares.persistentDictionaries.put(thisProc, pending);
pending.accept(c);
SharedRegistry sr = pending.objects;
// exclude the global scope
for (int scope = 0; scope < sr.shared.size() - 1; scope++)
{
Map<String, Object> dict = sr.shared.getDictionaryAtScope(scope, false);
if (dict == null)
{
continue;
}
for (Object o : dict.values())
{
AtomicInteger count = (AtomicInteger) pshares.refs.get(o);
if (count == null)
{
pshares.refs.put(o, count = new AtomicInteger(1));
}
else
{
count.incrementAndGet();
}
}
}
}
}
/**
* Provides a notification that a scope is about to be exited.
* <p>
* This is a no-op for the shared variable manager.
*/
public void scopeFinished()
{
// no-op
}
/**
* Provides notification that the external procedure scope has been deleted.
*/
@Override
public void scopeDeleted()
{
Object referent = wa.pm.getProcessedProcedure();
if (wa.tm.isExternalBlock() &&
!wa.tm.isGlobalBlock() &&
referent == wa.pm._thisProcedure() &&
!wa.pm._isPersistentAndNotPendingDelete(referent))
{
for (SharedRegistry sr : wa.resources)
{
// menu and frames can't be in a persistent procedures, so 'scopeDeleted' will be called when
// the external program ends. but scopeDeleted gets called when the external program ends, and
// this-procedure was persistent but already deleted within the external program - so we need to
// check if the referent is not persistent.
sr.shared.deleteScope();
}
}
if (referent == null ||
wa.pm._isPersistentAndNotPendingDelete(referent) ||
(referent == wa.pm._thisProcedure() && !wa.tm.isExternalBlock()))
{
return;
}
// this needs to be executed only when the procedure gets deleted.
if (referent != null)
{
for (PersistentShares pshare : wa.persistentResources)
{
PersistentRegistry reg =
(PersistentRegistry) pshare.persistentDictionaries.remove(referent);
if (reg == null)
{
continue;
}
// for all objects exposed via the shared registry of the deleted procedure, decrement
// their ref count when the ref count reaches zero, all these must be removed from the
// undoable list!
IdentityHashMap<LazyUndoable, Object> removed = new IdentityHashMap<>();
SharedRegistry sr = reg.objects;
// exclude the global scope
for (int scope = 0; scope < sr.shared.size() - 1; scope++)
{
Map<String, Object> dict = sr.shared.getDictionaryAtScope(scope, false);
if (dict == null)
{
continue;
}
for (Object o : dict.values())
{
AtomicInteger refs = (AtomicInteger) pshare.refs.get(o);
if (refs == null)
{
continue;
}
int count = refs.decrementAndGet();
if (count == 0)
{
pshare.refs.remove(o);
reg.collectUndoables(removed, o);
}
}
}
if (!removed.isEmpty())
{
TransactionManager.deregister(null, removed);
}
}
}
}
/**
* Get the {@link ScopeId} for the instance.
*
* @return {@link ScopeId#SHARED_VARIABLE_MANAGER}.
*/
@Override
public ScopeId getScopeId()
{
return ScopeId.SHARED_VARIABLE_MANAGER;
}
/**
* Cleanup the {@link WorkArea#bufferCreator} for the specific buffer instance.
*
* @param buffer
* The buffer instance.
*/
public static void cleanupBuffer(BufferReference buffer)
{
WorkArea wa = local.get().wa;
wa.bufferCreator.remove(buffer);
}
/**
* Update the references for the specified array, as a result of a resize.
*
* @param array
* The previous instance.
* @param copy
* The resized instance.
*/
static <T extends BaseDataType> void updateExtentVar(T[] array, T[] copy)
{
WorkArea wa = local.get().wa;
for (VarRegistry v : wa.vars.persistentDictionaries.values())
{
if (v == null)
{
continue;
}
SharedRegistry sr = v.objects;
int globalScope = sr.shared.size() - 1;
// search all scopes and update the variable; do not update the extentVars dictionary - this
// holds the original variable, to preserve its original extent, at the master definition
for (int scope = 0; scope < sr.shared.size(); scope++)
{
Map<String, Object> dict = sr.shared.getDictionaryAtScope(scope, false);
if (dict == null)
{
continue;
}
if (dict.values().contains(array))
{
// find its key (we don' know it here...)
String foundKey = null;
for (String key : dict.keySet())
{
Object val = dict.get(key);
if (val == array)
{
foundKey = key;
break;
}
}
dict.put(foundKey, copy);
if (scope == globalScope)
{
for (LazyUndoable undoable : copy)
{
undoable.setGlobal(true);
}
}
// break on first found, as only one reference must exist
break;
}
}
}
}
/**
* Cleanup the pending shared resources, as external program instantiation failed.
*/
static void cleanupPending()
{
WorkArea wa = local.get().wa;
// TODO: is there any resource-specific cleanup which should be done?
// i.e. discard temp-tables?
for (SharedRegistry sr : wa.resources)
{
// FRAME: ??
// MENU: ??
sr.pending = null;
}
for (PersistentShares pshare : wa.persistentResources)
{
// VARIABLE: nothing to do, just clean
// STREAM: nothing to do, just clean
// BUFFER: ??
// TEMP-TABLE: ??
pshare.pending = null;
}
}
/**
* Core add worker that defers adding (using a pending list in the work area) if this is a
* <code>ScopeLevel.NEXT</code> request, otherwise the instance will be mapped into the current
* or global scope immediately.
*
* @param sr
* The {@link SharedRegistry} container where to add the var.
* @param scope
* The scope to target.
* @param name
* The symbolic name for the shared instance.
* @param var
* The instance to be shared.
*/
private static void addWorker(SharedRegistry sr, ScopeLevel scope, String name, Object var)
{
SharedVariableManager mgr = local.get();
mgr.wa.tm.registerPendingScopeable(mgr);
if (scope == ScopeLevel.NEXT)
{
if (sr.pending == null)
{
sr.pending = new HashMap<>();
}
// we don't have to worry about the global flag as these can't be global, just save off
// the case-insensitive name and instance
sr.pending.put(sr.shared.isCaseSensitive() ? name : name.toLowerCase(), var);
}
else
{
sr.shared.addSymbol((scope == ScopeLevel.GLOBAL), name, var);
}
}
/**
* Core lookup worker that returns the first instance found by the
* given name. The order of lookup is:
* <ol>
* <li> pending list
* <li> each of the scopes starting at the current scope and moving
* through the stack to the global scope (if it exists)
* </ol>
*
* @param sr
* The {@link SharedRegistry} container where to add the var.
* @param name
* The symbolic name for the shared instance.
* @param error
* If no instance is found, use this to construct and throw the ERROR condition.
*
* @return The found resource, from the given context-local registry.
*/
private static Object lookupWorker(SharedRegistry sr, String name, Consumer<String> error)
throws ErrorConditionException
{
Object result = null;
if (sr.pending != null)
{
// names are always case-insensitive
result = sr.pending.get(sr.shared.isCaseSensitive() ? name : name.toLowerCase());
}
if (result == null)
{
result = sr.shared.lookupSymbol(name);
}
if (result == null && error != null)
{
error.accept(name);
}
return result;
}
/**
* Core lookup worker that returns the first instance found by the given name. This is used
* for resources which can survive the persistent scope. The current procedure's state is
* walked from the top of the stack down, with each scope performing this search:
* <ol>
* <li> pending list
* <li> each of the scopes starting at the current scope and moving
* through the stack to the global scope (if it exists)
* </ol>
*
* @param pshare
* The {@link PersistentShares} container where to add the object.
* @param name
* The symbolic name for the shared instance.
* @param error
* If no instance is found, use this to construct and throw the ERROR condition.
*
* @return The found resource, from the given context-local registry.
*/
private static Object lookupWorker(PersistentShares<?> pshare, String name, Consumer<String> error)
{
PersistentRegistry reg = pshare.lookupRegistry(name);
if (reg == null)
{
if (error != null)
{
error.accept(name);
}
return null;
}
return lookupWorker(reg.objects, name, error);
}
/**
* Simple helper to construct a {@link ErrorConditionException} and throw it, when needed.
*
* @param num
* The error number.
* @param msg
* The error message.
*
* @throws ErrorConditionException
* Always, this will be caught by ControlFlowOps and decide to log or re-throw
* the error.
*/
private static void errorHelper(int num, String msg)
throws ErrorConditionException
{
// this is used when there is a problem resolving a shared resource; we explicitly throw
// here because we assume this will be caught by ControlFlowOps$ExternalProgramResolver.resolve
// which catches all errors and dispatches them via ErrorManager.recordOrThrowError; it is
// safe to generate this exception directly
ErrorConditionException ece = new ErrorConditionException(num, msg);
throw ece;
}
/**
* Check if the shared temp-table DMO class matches the found master temp-table.
*
* @param found
* The found master temp-table.
* @param name
* The shared temp-table name.
* @param expected
* The expected temp-table to be defined by the found master.
*/
private static void checkSharedTempTable(Object found,
String name,
Class<? extends Temporary> expected)
{
TemporaryBuffer childBuffer = found == null ? null : (TemporaryBuffer) ((BufferReference) found).buffer();
Class<? extends DataModelObject> foundDmo = found == null ? null : childBuffer.getDMOInterface();
BooleanSupplier pred = () -> found != null && DmoMetadataManager.isSchemaMatch(expected, foundDmo);
Runnable error = () ->
{
String msg =
"In procedure %s, shared temp-table %s has a conflict in field, index or undo status";
String pname = ProcedureManager.getInstantiatingExternalProgram();
msg = String.format(msg, pname, name);
errorHelper(2075, msg);
};
if (!checkSharedResource(pred, error))
{
throw new IllegalStateException(
"An exception should have been thrown by checkedSharedResource!");
}
}
/**
* Check if the found legacy 4GL object is compatible with the expected one.
* <p>
* Regardless of extent vars or scalar vars, the 4GL class specified at the definition must
* match exactly.
*
* @param name
* The shared variables name.
* @param found
* The found legacy 4GL object.
* @param expected
* An instance describing the expected object.
*/
@SuppressWarnings("unchecked")
private static void checkObjectType(String name,
Object found,
Object expected)
{
Class<? extends _BaseObject_> expectedType = null;
Class<? extends _BaseObject_> foundType = null;
if (found instanceof object)
{
foundType = ((object<? extends _BaseObject_>) found).type();
expectedType = ((object<? extends _BaseObject_>) expected).type();
}
else if (found instanceof object[])
{
object<? extends _BaseObject_>[] fref = (object<? extends _BaseObject_>[]) found;
object<? extends _BaseObject_>[] eref = (object<? extends _BaseObject_>[]) expected;
foundType = fref.length == 0 ? ArrayAssigner.getObjectType(fref)
: fref[0].type();
expectedType = eref.length == 0 ? ArrayAssigner.getObjectType(eref)
: eref[0].type();
}
if (expectedType != foundType)
{
// the object's underlying type must match exactly
String msg = "Classes don't match for shared variable %s in %s";
String pname = ProcedureManager.getInstantiatingExternalProgram();
msg = String.format(msg, name, pname);
errorHelper(13450, msg);
}
}
/**
* Check if the defined shared variable matches the existing master variable.
*
* @param name
* The shared variables name.
* @param var
* The shared var instance (which is attempting to be defined).
* @param noUndo
* <code>true</code> if this variable has its NO-UNDO option set.
*/
private static void checkSharedVariable(String name, Object var, boolean noUndo)
{
BooleanSupplier pred = () -> (variablesMatch(name, var, noUndo));
Runnable error = () ->
{
String msg = "%s Conflict in extent, datatype, or undo status for shared %s";
String pname = ProcedureManager.getInstantiatingExternalProgram();
msg = String.format(msg, pname, name);
errorHelper(391, msg);
};
if (!checkSharedResource(pred, error))
{
throw new IllegalStateException(
"An exception should have been thrown by checkedSharedResource!");
}
}
/**
* Check if the candidate slave shared variable matches an existing master variable.
*
* @param name
* The shared var name.
* @param var
* The slave variable which needs to match the master one.
* @param noUndo
* Flag indicating if the slave var was defined as NO-UNDO.
*/
private static boolean variablesMatch(String name, Object var, boolean noUndo)
{
WorkArea wa = local.get().wa;
VarRegistry v = wa.vars.lookupRegistry(name);
SharedRegistry sr = (var.getClass().isArray() ? v.extentVars : v.objects);
Object shared = sr.shared.lookupSymbol(name);
if (shared == null)
{
return false;
}
// check if variable matches the registered one
boolean canAssign = BaseDataType.sameType(shared, var);
if (canAssign)
{
// check no-undo
SharedRegistry noUndoWa = v.noUndoVars;
Object sharedNoUndoVar = noUndoWa.shared.lookupSymbol(name);
boolean sharedNoUndo = (sharedNoUndoVar != null &&
((Boolean)sharedNoUndoVar).booleanValue());
if (sharedNoUndo != noUndo)
{
canAssign = false;
}
}
return canAssign;
}
/**
* Check if the specified match condition is <code>true</code>. If it is not, it will execute
* the specified error code.
*
* @param pred
* The predicate to evaluate if the resource matches the found master.
* @param error
* The error code to execute.
*
* @return <code>true</code> if the resource matches the master.
*/
private static boolean checkSharedResource(BooleanSupplier pred, Runnable error)
{
if (!pred.getAsBoolean())
{
error.run();
return false;
}
return true;
}
/**
* Searches all scopes from the top of the stack down for the first temp
* table with the given name.
*
* @param <T>
* Variable type.
* @param name
* The temp table buffer name.
* @param expected
* The expected DMO type of the resolved temp-table.
* @param error
* Error action to b executed if no instance is found.
*
* @return The temp table buffer reference associated with the name or
* <code>null</code> if not found.
*/
private static <T extends Temporary> T lookupTempTable(String name,
Class<T> expected,
Consumer<String> error)
throws ErrorConditionException
{
WorkArea wa = local.get().wa;
T found = (T) lookupWorker(wa.tempTables, name, error);
if (found == null)
{
// fail early if it is not found
return null;
}
if (expected != null)
{
checkSharedTempTable(found, name, expected);
}
PersistentRegistry vr = wa.tempTables.getRegistry(ScopeLevel.NEXT, PersistentRegistry::new);
SharedRegistry sr = vr.objects;
addWorker(sr, ScopeLevel.NEXT, name, found);
PersistentRegistry ttNamesReg = wa.tempTableNames.getRegistry(ScopeLevel.NEXT, PersistentRegistry::new);
addWorker(ttNamesReg.objects, ScopeLevel.NEXT, name, name);
return found;
}
/**
* Stores the state of the current context.
*/
private static class WorkArea
{
/** Helper to use the ProcedureManager without any context local lookups. */
private final ProcedureManager.ProcedureHelper pm = ProcedureManager.getProcedureHelper();
/** Helper to use the TM without any context local lookups. */
private TransactionManager.TransactionHelper tm = TransactionManager.getTransactionHelper();
/** Map with each buffer's external program, where it was defined. */
private Map<Object, Object> bufferCreator = new IdentityHashMap<>();
private PersistentShares<VarRegistry> vars = new PersistentShares<>(new VarRegistry());
/** Pool of streams which is context-local. Initializes upon first use. */
private PersistentShares<PersistentRegistry> streams =
new PersistentShares<>(new PersistentRegistry());
/** Pool of buffers which is context-local. Initializes upon first use. */
private PersistentShares<BufferRegistry> buffers =
new PersistentShares<>(new BufferRegistry());
/** Pool of queries which is context-local. Initializes upon first use. */
private PersistentShares<PersistentRegistry> queries =
new PersistentShares<>(new PersistentRegistry());
/** Temp-table pool which is context-local. Initializes upon first use. */
private PersistentShares<PersistentRegistry> tempTables =
new PersistentShares<>(new PersistentRegistry(true));
/**
* Temp-table names pool which is context-local. Initializes upon first use. Contains
* legacy name -> variable name (same as converted name) mapping for {@link #tempTables}.
*/
private PersistentShares<PersistentRegistry> tempTableNames =
new PersistentShares<>(new PersistentRegistry());
/** Pool of frames which is context-local. Initializes upon first use. */
private SharedRegistry frames = new SharedRegistry();
/** Pool of menus which is context-local. Initializes upon first use. */
private SharedRegistry menus = new SharedRegistry(true);
/** All the shared data. */
private SharedRegistry[] resources =
{
frames,
menus
};
/** All the shared data which survives with the persistent procedure. */
private PersistentShares<?>[] persistentResources =
{
vars,
streams,
buffers,
tempTables,
tempTableNames,
queries
};
}
/**
* Container for the registries related to shared variables.
*/
private static class VarRegistry
extends PersistentRegistry
{
/** Pool of extent vars which is context-local (with the state at their definition). */
private final SharedRegistry extentVars = new SharedRegistry();
/** Pool of NO-UNDO vars which is context-local (with the state at their definition). */
private final SharedRegistry noUndoVars = new SharedRegistry();
/** All the shared data. */
private SharedRegistry[] resources =
{
objects,
extentVars,
noUndoVars
};
/**
* Clone this instance.
*
* @return A copy of this instance.
*/
@Override
public VarRegistry clone()
{
VarRegistry var = new VarRegistry();
for (int i = 0; i < resources.length; i++)
{
var.resources[i].shared.copyAll(resources[i].shared, true);
}
return var;
}
/**
* Accept the specified consumer over all resource registries.
*
* @param c
* The operation to perform.
*/
@Override
public void accept(Consumer<SharedRegistry> c)
{
for (SharedRegistry sr : resources)
{
c.accept(sr);
}
}
/**
* Collect the undoables for this resource in the specified map.
*
* @param removed
* The map where to add the undoables.
* @param o
* The original object.
*/
@Override
public void collectUndoables(IdentityHashMap<LazyUndoable, Object> removed, Object o)
{
if (o instanceof BaseDataType)
{
removed.put((BaseDataType) o, null);
}
else
{
for (BaseDataType v : (BaseDataType[]) o)
{
removed.put(v, null);
}
}
}
}
/**
* A registry for BUFFER resources.
*/
private static class BufferRegistry
extends PersistentRegistry
{
/**
* Default c'tor. Initialize the dictionaries as case-sensitive.
*/
public BufferRegistry()
{
super(true);
}
/**
* Collect the undoables for this resource in the specified map.
*
* @param removed
* The map where to add the undoables.
* @param o
* The original object.
*/
@Override
public void collectUndoables(IdentityHashMap<LazyUndoable, Object> removed, Object o)
{
LazyUndoable undoable = ((BufferReference) o).buffer().getUndoable();
if (undoable != null)
{
removed.put(undoable, null);
}
}
/**
* Clone this instance.
*
* @return A copy of this instance.
*/
@Override
protected PersistentRegistry clone()
{
PersistentRegistry reg = new BufferRegistry();
reg.objects.shared.copyAll(objects.shared, true);
return reg;
}
}
/**
* A registry for resources which exist with the persistent procedure.
*/
private static class PersistentRegistry
implements Cloneable
{
/** Object pool which is context-local. Initializes upon first use. */
protected final SharedRegistry objects;
/**
* Default c'tor. Initialize the dictionaries as case-insensitive.
*/
public PersistentRegistry()
{
this(false);
}
/**
* Initialize the dictionaries with the provided case-sensitivity.
*
* @param caseSensitive
* Flag indicating if the dictionary keys are case-sensitive or not.
*/
public PersistentRegistry(boolean caseSensitive)
{
this.objects = new SharedRegistry(caseSensitive);
}
/**
* Accept the specified consumer over all resource registries.
*
* @param c
* The operation to perform.
*/
public void accept(Consumer<SharedRegistry> c)
{
c.accept(objects);
}
/**
* Collect the undoables for this resource in the specified map. By default is a no-op.
*
* @param removed
* The map where to add the undoables.
* @param o
* The original object.
*/
public void collectUndoables(IdentityHashMap<LazyUndoable, Object> removed, Object o)
{
// no-op
}
/**
* Clone this instance.
*
* @return A copy of this instance.
*/
@Override
protected PersistentRegistry clone()
{
PersistentRegistry reg = new PersistentRegistry(objects.shared.isCaseSensitive());
reg.objects.shared.copyAll(objects.shared, true);
return reg;
}
}
/**
* Common behavior for all shared resources which are in sync with the persistent procedure
* state.
*/
private static class PersistentShares<T extends PersistentRegistry>
{
/** The persistent dictionaries, saved per each external program. */
private final Map<Object, T> persistentDictionaries = new IdentityHashMap<>();
/**
* Track references for all shared objects, in all registries (not defined/used) - when
* the reference count reaches zero, they will be removed from the transaction-related
* registries.
*/
private final IdentityHashMap<Object, AtomicInteger> refs = new IdentityHashMap<>();
/** The objects being defined in the currently instantiating external program. */
private PersistentRegistry pending = null;
/** Global pool which is context-local. */
private final T globals;
/**
* Initialize this container with the specified global registry, which will be used as a
* seed for all other scopes.
*/
private PersistentShares(T globals)
{
this.globals = globals;
}
/**
* Resolve the registry: if the scope is {@link ScopeLevel#NEXT}, return the {@link #globals} registry.
* If the scope is {@link ScopeLevel#NEXT}, return the {@link #pending} registry.
* <p>
* Otherwise, return the registry associated with the current {@link ProcedureManager#thisProcedure()}.
*
* @param scope
* The scope for which the registry is needed.
* @param newRegistry
* A function to initiliaze the {@link #pending} or this-procedure's registry, if needed.
*
* @return The {@link #pending} registry.
*/
public T getRegistry(ScopeLevel scope, Supplier<T> newRegistry)
{
if (scope == ScopeLevel.GLOBAL)
{
return this.globals;
}
if (scope == ScopeLevel.NEXT)
{
if (this.pending == null)
{
this.pending = newRegistry.get();
}
return (T) this.pending;
}
WorkArea wa = local.get().wa;
Object referent = wa.pm._thisProcedure();
T reg = persistentDictionaries.computeIfAbsent(referent, (o) -> newRegistry.get());
return reg;
}
/**
* Lookup a {@link PersistentRegistry} which contains a shared variable with the given name;
* this starts from the top of the stack and searches in the context of each
* {@link #persistentDictionaries referent} for the given variable name.
*
* @param name
* The shared object name.
*
* @return The found {@link PersistentRegistry} or <code>null</code> if it can't be found.
*/
public T lookupRegistry(String name)
{
Function<Object, Object> search = (referent) ->
{
T vreg = persistentDictionaries.get(referent);
if (vreg == null)
{
return null;
}
return (lookupWorker(vreg.objects, name, null) != null) ? vreg : null;
};
// search previous scopes...
T reg = (T) ProcedureManager.searchInStack(search);
if (reg == null)
{
// default to global lookup
reg = globals;
}
return reg;
}
}
/**
* Stores the registry for a specified shared resource.
*/
private static class SharedRegistry
{
/**
* Instances mapped by case-insensitive name and organized by scope. Use this version of
* the constructor so that a global scope is added during construction.
*/
private final ScopedSymbolDictionary<Object> shared;
/** Instances that must be added to the NEXT scope. */
private Map<String, Object> pending = null;
/**
* Initialize the registry with a case-insensitive {@link #shared dictionary}.
*/
private SharedRegistry()
{
this(false);
}
/**
* Initialize the registry.
*
* @param caseSensitive
* Flag indicating if the {@link #shared dictionary} is case sensitive or not; a
* registry which has as keys java names must always be case-sensitive. If it is
* using legacy names, then it must be case-insensitive.
*/
private SharedRegistry(boolean caseSensitive)
{
shared = new ScopedSymbolDictionary<Object>(null, caseSensitive);
}
}
}