From: <jsh...@rh...> - 2009-12-01 21:19:50
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head><style type="text/css"><!-- #msg DL { border : 1px #006 solid; background-color : #369; padding : 6px; color : #fff; } #msg DT { float : left; width : 6em; font-weight : bold; } #msg DL, #msg DT, #msg UL, #msg LI { font-family : arial,helvetica,sans-serif; font-size : 10pt; } h3 { font-family : arial,helvetica,sans-serif; font-size : 10pt; font-weight : bold; } #msg PRE { overflow : auto; white-space : normal; background-color : #ffc; border : 1px #fc0 solid; padding : 6px; } #msg UL, PRE, .diff { overflow : auto; } #patch h4 { font-family : arial,helvetica,sans-serif; font-size : 10pt; } #patch h4 { padding: 8px; background : #369; color : #fff; margin : 0; } #patch .propset h4, #patch .binary h4 {margin: 0;} #patch pre {padding:0;line-height:1.2em;margin:0;} #patch .diff {background:#eeeeee;padding: 0 0 10px 0;} #patch .propset .diff, #patch .binary .diff {padding: 10px 0;} #patch span {display:block;padding:0 10px;} #patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;} #patch .add {background:#ddffdd;} #patch .rem {background:#ffdddd;} #patch .lines, .info {color:#888888;background:#ffffff;} .diff { width : 100%; } #msg DL { border : 1px #006 solid; background-color : #369; padding : 6px; color : #fff; } #msg DT { float : left; width : 6em; font-weight : bold; } #msg DL, #msg DT, #msg UL, #msg LI { font-family : arial,helvetica,sans-serif; font-size : 10pt; } h3 { font-family : arial,helvetica,sans-serif; font-size : 10pt; font-weight : bold; } #msg PRE { overflow : auto; white-space : normal; background-color : #ffc; border : 1px #fc0 solid; padding : 6px; } #msg UL, PRE, .diff { overflow : auto; } #patch h4 { font-family : arial,helvetica,sans-serif; font-size : 10pt; } #patch h4 { padding: 8px; background : #369; color : #fff; margin : 0; } #patch .propset h4, #patch .binary h4 {margin: 0;} #patch pre {padding:0;line-height:1.2em;margin:0;} #patch .diff {background:#eeeeee;padding: 0 0 10px 0;} #patch .propset .diff, #patch .binary .diff {padding: 10px 0;} #patch span {display:block;padding:0 10px;} #patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;} #patch .add {background:#ddffdd;} #patch .rem {background:#ffdddd;} #patch .lines, .info {color:#888888;background:#ffffff;} .diff { width : 100%; } --></style> <title>[rhq-project.org rhq] [5266] Fix for RHQ-2479 (https://bugzilla.redhat.com/show_bug.cgi?id=RHQ-2479)</title> </head> <body> <div id="msg"> <dl> <dt>Revision</dt> <dd>5266</dd> <dt>Author</dt> <dd>jshaughn</dd> <dt>Date</dt> <dd>2009-12-01 15:19:19 -0600 (Tue, 01 Dec 2009)</dd> </dl> <h3>Log Message</h3> <pre>Fix for RHQ-2479 (https://bugzilla.redhat.com/show_bug.cgi?id=RHQ-2479)</pre> <h3>Modified Paths</h3> <ul> <li><a href="#rhqbranchesRHQ_1_2_0_GA_PERFmodulescoredomainsrcmainjavaorgrhqcoredomainmeasurementMeasurementDefinitionjava">rhq/branches/RHQ_1_2_0_GA_PERF/modules/core/domain/src/main/java/org/rhq/core/domain/measurement/MeasurementDefinition.java</a></li> <li><a href="#rhqbranchesRHQ_1_2_0_GA_PERFmodulesenterpriseserverjarsrcmainjavaorgrhqenterpriseservermeasurementMeasurementScheduleManagerBeanjava">rhq/branches/RHQ_1_2_0_GA_PERF/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/measurement/MeasurementScheduleManagerBean.java</a></li> </ul> </div> <div id="patch"> <h3>Diff</h3> <a id="rhqbranchesRHQ_1_2_0_GA_PERFmodulescoredomainsrcmainjavaorgrhqcoredomainmeasurementMeasurementDefinitionjava"></a> <div class="modfile"><h4>Modified: rhq/branches/RHQ_1_2_0_GA_PERF/modules/core/domain/src/main/java/org/rhq/core/domain/measurement/MeasurementDefinition.java (5265 => 5266)</h4> <pre class="diff"> <span class="info">--- rhq/branches/RHQ_1_2_0_GA_PERF/modules/core/domain/src/main/java/org/rhq/core/domain/measurement/MeasurementDefinition.java 2009-11-13 04:57:25 UTC (rev 5265) +++ rhq/branches/RHQ_1_2_0_GA_PERF/modules/core/domain/src/main/java/org/rhq/core/domain/measurement/MeasurementDefinition.java 2009-12-01 21:19:19 UTC (rev 5266) </span><span class="lines">@@ -68,6 +68,23 @@ </span><span class="cx"> @Table(name = "RHQ_MEASUREMENT_DEF") public class MeasurementDefinition implements Serializable { </span><span class="add">+ public static final String QUERY_NATIVE_UPDATE_DEFAULT_ON_BY_IDS = "" // + + "UPDATE RHQ_MEASUREMENT_DEF" // + + " SET DEFAULT_ON = ?" // + + " WHERE ID IN ( @@DEFINITION_IDS@@ )"; + public static final String QUERY_NATIVE_UPDATE_DEFAULTS_BY_IDS = "" // + + "UPDATE RHQ_MEASUREMENT_DEF" // + + " SET DEFAULT_ON = ?, DEFAULT_INTERVAL = ?" // + + " WHERE ID IN ( @@DEFINITION_IDS@@ )"; + public static final String QUERY_NATIVE_UPDATE_SCHEDULES_ENABLE_BY_IDS = "" // + + "UPDATE RHQ_MEASUREMENT_SCHED" // + + " SET ENABLED = ?" // + + " WHERE DEFINITION IN ( @@DEFINITION_IDS@@ )"; + public static final String QUERY_NATIVE_UPDATE_SCHEDULES_BY_IDS = "" // + + "UPDATE RHQ_MEASUREMENT_SCHED" // + + " SET ENABLED = ?, COLL_INTERVAL = ?" // + + " WHERE DEFINITION IN ( @@DEFINITION_IDS@@ )"; + </span><span class="cx"> private static final long serialVersionUID = 1L; /** </span><span class="lines">@@ -420,13 +437,11 @@ </span><span class="cx"> return rawNumericType != null; } </span><span class="rem">- public NumericType getRawNumericType() - { </span><span class="add">+ public NumericType getRawNumericType() { </span><span class="cx"> return rawNumericType; } </span><span class="rem">- public void setRawNumericType(NumericType rawNumericType) - { </span><span class="add">+ public void setRawNumericType(NumericType rawNumericType) { </span><span class="cx"> this.rawNumericType = rawNumericType; } </span></pre></div> <a id="rhqbranchesRHQ_1_2_0_GA_PERFmodulesenterpriseserverjarsrcmainjavaorgrhqenterpriseservermeasurementMeasurementScheduleManagerBeanjava"></a> <div class="modfile"><h4>Modified: rhq/branches/RHQ_1_2_0_GA_PERF/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/measurement/MeasurementScheduleManagerBean.java (5265 => 5266)</h4> <pre class="diff"> <span class="info">--- rhq/branches/RHQ_1_2_0_GA_PERF/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/measurement/MeasurementScheduleManagerBean.java 2009-11-13 04:57:25 UTC (rev 5265) +++ rhq/branches/RHQ_1_2_0_GA_PERF/modules/enterprise/server/jar/src/main/java/org/rhq/enterprise/server/measurement/MeasurementScheduleManagerBean.java 2009-12-01 21:19:19 UTC (rev 5266) </span><span class="lines">@@ -569,13 +569,13 @@ </span><span class="cx"> public void disableMeasurementSchedules(Subject subject, int[] measurementDefinitionIds, int resourceId) { if (!disableMeasurementSchedulesInternal(subject, measurementDefinitionIds, resourceId)) { </span><span class="rem">- Resource resource = resourceManager.getResourceById(subject, resourceId); - Set<Resource> failedSyncResources = new HashSet<Resource>(); - failedSyncResources.add(resource); - handleFailedSyncResources(failedSyncResources); </span><span class="add">+ Resource resource = resourceManager.getResourceById(subject, resourceId); + Set<Resource> failedSyncResources = new HashSet<Resource>(); + failedSyncResources.add(resource); + handleFailedSyncResources(failedSyncResources); </span><span class="cx"> } } </span><span class="rem">- </span><span class="add">+ </span><span class="cx"> private boolean disableMeasurementSchedulesInternal(Subject subject, int[] measurementDefinitionIds, int resourceId) { Resource resource = resourceManager.getResourceById(subject, resourceId); if (!authorizationManager.hasResourcePermission(subject, Permission.MANAGE_MEASUREMENTS, resourceId)) { </span><span class="lines">@@ -658,79 +658,144 @@ </span><span class="cx"> return; } </span><span class="rem">- List<MeasurementDefinition> measurementDefinitions = getDefinitionsByIds(measurementDefinitionIds); - for (MeasurementDefinition measurementDefinition : measurementDefinitions) { - if (collectionInterval > 0) { - measurementDefinition.setDefaultOn(true); - measurementDefinition.setDefaultInterval(collectionInterval); - } else { - measurementDefinition.setDefaultOn(false); - } </span><span class="add">+ boolean enableDisable = (collectionInterval > 0); + + // batch the modifications to prevent the ORA error about IN clauses containing more than 1000 items + for (int batchIndex = 0; (batchIndex < measurementDefinitionIds.length); batchIndex += 1000) { + int[] batchIdArray = copyOfRange(measurementDefinitionIds, batchIndex, batchIndex + 1000); + + modifyDefaultCollectionIntervalForMeasurementDefinitions(subject, batchIdArray, enableDisable, + collectionInterval, updateExistingSchedules); </span><span class="cx"> } </span><span class="add">+ } </span><span class="cx"> </span><span class="rem">- Map<Integer, ResourceMeasurementScheduleRequest> reqMap = new HashMap<Integer, ResourceMeasurementScheduleRequest>(); - if (updateExistingSchedules) { - for (MeasurementDefinition measurementDefinition : measurementDefinitions) { - // check if schedules need to be updated as well </span><span class="add">+ /** + * @param measurementDefinitionIds 1 <= length <= 1000. + */ + @SuppressWarnings("unchecked") + private void modifyDefaultCollectionIntervalForMeasurementDefinitions(Subject subject, + int[] measurementDefinitionIds, boolean enableDisable, long collectionInterval, boolean updateExistingSchedules) { </span><span class="cx"> </span><span class="rem">- List<MeasurementSchedule> schedules = measurementDefinition.getSchedules(); - for (MeasurementSchedule sched : schedules) { - if (collectionInterval > 0) { - sched.setEnabled(true); - sched.setInterval(collectionInterval); - } else { - sched.setEnabled(false); - } </span><span class="add">+ // this method has been rewritten to ensure that the Hibernate cache is not utilized in an + // extensive way, regardless of the number of measurementDefinitionIds being processed. Future + // enhancements must keep this in mind and avoid using attached objects. </span><span class="cx"> </span><span class="rem">- // Create update requests to feed to the agents - int resourceId = sched.getResource().getId(); - if (!reqMap.containsKey(resourceId)) { - ResourceMeasurementScheduleRequest req = new ResourceMeasurementScheduleRequest(resourceId); </span><span class="add">+ // update all of the measurement definitions via native query to avoid Hibernate caching + Connection conn = null; + PreparedStatement defUpdateStmt = null; + PreparedStatement schedUpdateStmt = null; + String queryString = null; + int i; + try { + conn = dataSource.getConnection(); + + // update the defaults on the measurement definitions + queryString = (collectionInterval > 0L) ? MeasurementDefinition.QUERY_NATIVE_UPDATE_DEFAULTS_BY_IDS + : MeasurementDefinition.QUERY_NATIVE_UPDATE_DEFAULT_ON_BY_IDS; + + String transformedQuery = JDBCUtil.transformQueryForMultipleInParameters(queryString, "@@DEFINITION_IDS@@", + measurementDefinitionIds.length); + defUpdateStmt = conn.prepareStatement(transformedQuery); + i = 1; + defUpdateStmt.setBoolean(i++, enableDisable); + if (collectionInterval > 0L) { + defUpdateStmt.setLong(i++, collectionInterval); + } + JDBCUtil.bindNTimes(defUpdateStmt, measurementDefinitionIds, i++); + defUpdateStmt.executeUpdate(); + + if (updateExistingSchedules) { + Map<Integer, ResourceMeasurementScheduleRequest> reqMap = new HashMap<Integer, ResourceMeasurementScheduleRequest>(); + List<Integer> idsAsList = wrapInList(measurementDefinitionIds); + + // update the schedules associated with the measurement definitions (i.e. the current inventory) + queryString = (collectionInterval > 0L) ? MeasurementDefinition.QUERY_NATIVE_UPDATE_SCHEDULES_BY_IDS + : MeasurementDefinition.QUERY_NATIVE_UPDATE_SCHEDULES_ENABLE_BY_IDS; + + transformedQuery = JDBCUtil.transformQueryForMultipleInParameters(queryString, "@@DEFINITION_IDS@@", + measurementDefinitionIds.length); + schedUpdateStmt = conn.prepareStatement(transformedQuery); + i = 1; + schedUpdateStmt.setBoolean(i++, enableDisable); + if (collectionInterval > 0L) { + schedUpdateStmt.setLong(i++, collectionInterval); + } + JDBCUtil.bindNTimes(schedUpdateStmt, measurementDefinitionIds, i++); + schedUpdateStmt.executeUpdate(); + + // Notify the agents of the updated schedules for affected resources + + // we need specific information to construct the agent updates. This query is specific to + // this use case and therefore is define here and not in a domain module. Note that this + // query must not return domain entities as they would be placed in the Hibernate cache. + // Return only the data necessary to construct minimal objects ourselves. Using JPQL + // is ok, it just lets Hibernate do the heavy lifting for query generation. + queryString = "" // + + "SELECT ms.id, ms.resource.id, ms.definition.name, ms.definition.dataType, ms.definition.numericType" // + + " FROM MeasurementSchedule ms" // + + " WHERE ms.definition.id IN ( :definitionIds )"; + Query query = entityManager.createQuery(queryString); + query.setParameter("definitionIds", idsAsList); + List<Object[]> rs = query.getResultList(); + + for (Object[] row : rs) { + i = 0; + int schedId = (Integer) row[i++]; + int resourceId = (Integer) row[i++]; + String name = (String) row[i++]; + DataType dataType = (DataType) row[i++]; + NumericType numericType = (NumericType) row[i++]; + + ResourceMeasurementScheduleRequest req = reqMap.get(resourceId); + if (null == req) { + req = new ResourceMeasurementScheduleRequest(resourceId); </span><span class="cx"> reqMap.put(resourceId, req); } </span><span class="rem">- - ResourceMeasurementScheduleRequest req = reqMap.get(resourceId); - MeasurementScheduleRequest msr = new MeasurementScheduleRequest(sched); </span><span class="add">+ MeasurementScheduleRequest msr = new MeasurementScheduleRequest(schedId, name, collectionInterval, + enableDisable, dataType, numericType); </span><span class="cx"> req.addMeasurementScheduleRequest(msr); } </span><span class="rem">- } </span><span class="cx"> </span><span class="rem">- Map<Agent, Set<ResourceMeasurementScheduleRequest>> agentUpdates = null; - agentUpdates = new HashMap<Agent, Set<ResourceMeasurementScheduleRequest>>(); </span><span class="add">+ Map<Agent, Set<ResourceMeasurementScheduleRequest>> agentUpdates = null; + agentUpdates = new HashMap<Agent, Set<ResourceMeasurementScheduleRequest>>(); </span><span class="cx"> </span><span class="rem">- for (Integer resourceId : reqMap.keySet()) { - Agent agent = agentManager.getAgentByResourceId(resourceId); </span><span class="add">+ // The number of Agents is manageable, so we can work with entities here + for (Integer resourceId : reqMap.keySet()) { + Agent agent = agentManager.getAgentByResourceId(resourceId); </span><span class="cx"> </span><span class="rem">- Set<ResourceMeasurementScheduleRequest> agentUpdate = agentUpdates.get(agent); - if (agentUpdate == null) { - agentUpdate = new HashSet<ResourceMeasurementScheduleRequest>(); - agentUpdates.put(agent, agentUpdate); </span><span class="add">+ Set<ResourceMeasurementScheduleRequest> agentUpdate = agentUpdates.get(agent); + if (agentUpdate == null) { + agentUpdate = new HashSet<ResourceMeasurementScheduleRequest>(); + agentUpdates.put(agent, agentUpdate); + } + + agentUpdate.add(reqMap.get(resourceId)); </span><span class="cx"> } </span><span class="rem">- agentUpdate.add(reqMap.get(resourceId)); </span><span class="add">+ // convert the int[] to Integer[], in case we need to set + List<Integer> measurementDefinitionList = idsAsList; + // send schedule updates to agents + for (Map.Entry<Agent, Set<ResourceMeasurementScheduleRequest>> agentEntry : agentUpdates.entrySet()) { + boolean synced = sendUpdatedSchedulesToAgent(agentEntry.getKey(), agentEntry.getValue()); + if (!synced) { + /* + * only sync resources that are affected by this set of definitions that were updated, and only + * for the agent that couldn't be contacted (under the assumption that 9 times out of 10 the agent + * will be up; so, we don't want to unnecessarily mark more resources as needing syncing that don't + */ + int agentId = agentEntry.getKey().getId(); + setAgentSynchronizationNeededByDefinitionsForAgent(agentId, measurementDefinitionList); + } + } </span><span class="cx"> } </span><span class="rem">- // convert the int[] to Integer[], incase we need to set - List<Integer> measurementDefinitionList = convertIntArrayToListOfIntegers(measurementDefinitionIds); - // send schedule updates to agents - Set<Agent> downAgents = new HashSet<Agent>(); - for (Map.Entry<Agent, Set<ResourceMeasurementScheduleRequest>> agentEntry : agentUpdates.entrySet()) { - Agent agent = agentEntry.getKey(); - boolean synced = sendUpdatedSchedulesToAgent(agent, agentEntry.getValue()); - if (!synced) { - /* - * only sync resources that are affected by this set of definitions that were updated, and only - * for the agent that couldn't be contacted (under the assumption that 9 times out of 10 the agent - * will be up; so, we don't want to unnecessarily mark more resources as needing syncing that don't - */ - setAgentSynchronizationNeededByDefinitionsForAgent(agent.getId(), measurementDefinitionList); - downAgents.add(agent); - } - } - if (!downAgents.isEmpty()) { - throw new RuntimeException("Failed to push updated schedules to Agents " + downAgents - + " - please make sure these Agents are started and reachable, then try again."); - } </span><span class="add">+ } catch (Exception e) { + log.error("Error updating measurement definitions: ", e); + throw new MeasurementException("Error updating measurement definitions: " + e.getMessage()); + } finally { + JDBCUtil.safeClose(defUpdateStmt); + JDBCUtil.safeClose(schedUpdateStmt); + JDBCUtil.safeClose(conn); </span><span class="cx"> } } </span><span class="lines">@@ -759,7 +824,7 @@ </span><span class="cx"> handleFailedSyncResources(failedSyncResources); } } </span><span class="rem">- </span><span class="add">+ </span><span class="cx"> public boolean updateMeasurementSchedulesInternal(Subject subject, int[] measurementDefinitionIds, int resourceId, long collectionInterval) { collectionInterval = verifyMinimumCollectionInterval(collectionInterval); </span><span class="lines">@@ -810,14 +875,15 @@ </span><span class="cx"> // don't verify minimum collection interval here, it will be caught by updateMeasurementSchedules callee List<Resource> resources = resourceGroupManager.getResourcesForAutoGroup(subject, parentResourceId, childResourceType); </span><span class="rem">- </span><span class="add">+ </span><span class="cx"> Set<Resource> failedSyncResources = new HashSet<Resource>(); for (Resource resource : resources) { </span><span class="rem">- if (!updateMeasurementSchedulesInternal(subject, measurementDefinitionIds, resource.getId(), collectionInterval)) { - failedSyncResources.add(resource); </span><span class="add">+ if (!updateMeasurementSchedulesInternal(subject, measurementDefinitionIds, resource.getId(), + collectionInterval)) { + failedSyncResources.add(resource); </span><span class="cx"> } } </span><span class="rem">- handleFailedSyncResources(failedSyncResources); </span><span class="add">+ handleFailedSyncResources(failedSyncResources); </span><span class="cx"> } /** </span><span class="lines">@@ -841,11 +907,12 @@ </span><span class="cx"> Set<Resource> failedSyncResources = new HashSet<Resource>(); for (Resource resource : resources) { </span><span class="rem">- if (!updateMeasurementSchedulesInternal(subject, measurementDefinitionIds, resource.getId(), collectionInterval)) { - failedSyncResources.add(resource); </span><span class="add">+ if (!updateMeasurementSchedulesInternal(subject, measurementDefinitionIds, resource.getId(), + collectionInterval)) { + failedSyncResources.add(resource); </span><span class="cx"> } } </span><span class="rem">- handleFailedSyncResources(failedSyncResources); </span><span class="add">+ handleFailedSyncResources(failedSyncResources); </span><span class="cx"> } /** </span><span class="lines">@@ -859,8 +926,8 @@ </span><span class="cx"> for (Resource resource : resources) { if (!disableMeasurementSchedulesInternal(subject, measurementDefinitionIds, resource.getId())) { failedSyncResources.add(resource); </span><span class="rem">- } - } </span><span class="add">+ } + } </span><span class="cx"> handleFailedSyncResources(failedSyncResources); } </span><span class="lines">@@ -876,7 +943,7 @@ </span><span class="cx"> int parentResourceId, int childResourceType) { List<Resource> resources = resourceGroupManager.getResourcesForAutoGroup(subject, parentResourceId, childResourceType); </span><span class="rem">- </span><span class="add">+ </span><span class="cx"> Set<Resource> failedSyncResources = new HashSet<Resource>(); for (Resource resource : resources) { if (!disableMeasurementSchedulesInternal(subject, measurementDefinitionIds, resource.getId())) { </span><span class="lines">@@ -1269,15 +1336,55 @@ </span><span class="cx"> } } } </span><span class="rem">- </span><span class="add">+ </span><span class="cx"> private void handleFailedSyncResources(Set<Resource> failedSyncResources) { if (!failedSyncResources.isEmpty()) { Set<Agent> downAgents = new HashSet<Agent>(); for (Resource failedSyncResource : failedSyncResources) { downAgents.add(failedSyncResource.getAgent()); </span><span class="rem">- } - throw new RuntimeException("Failed to push updated schedules to Agents for Resources " + failedSyncResources - + " - no response from Agents " + downAgents + " - please make sure these Agents are started and reachable, then try again."); - } </span><span class="add">+ } + throw new RuntimeException("Failed to push updated schedules to Agents for Resources " + + failedSyncResources + " - no response from Agents " + downAgents + + " - please make sure these Agents are started and reachable, then try again."); + } </span><span class="cx"> } </span><span class="add">+ + // from ArrayUtils in later version, backported here for patch + private int[] copyOfRange(int[] arr, int from, int to) { + if (to < from) { + throw new IllegalArgumentException(to + "<" + from); + } + int newSize = Math.min(arr.length - from, to - from); // to prevent null items in returned array + int[] copy = new int[newSize]; + System.arraycopy(arr, from, copy, 0, newSize); + return copy; + } + + // from ArrayUtils in later version, backported here for patch + private Integer[] wrapInArray(int[] input) { + if (input == null) { + return null; + } + Integer[] output = new Integer[input.length]; + for (int i = 0; i < input.length; i++) { + output[i] = input[i]; + } + return output; + } + + // from ArrayUtils in later version, backported here for patch + private List<Integer> wrapInList(int[] input) { + if (input == null) { + return null; + } + Integer[] intermediate = wrapInArray(input); + + // do not use Arrays.asList because returned list needs to be modifiable + List<Integer> results = new ArrayList<Integer>(); + for (Integer next : intermediate) { + results.add(next); + } + return results; + } + </span><span class="cx"> } \ No newline at end of file </span> </pre> </div> </div> </body> </html> |