Пакеты и пространства имен

Пакеты и пространства имен являются взаимосвязанными концепциями. Пакеты позволяют связывать определения классов вместе таким способом, что облегчается совместное использование кода и снижается вероятность конфликтов имен. Пространства имен позволяют управлять видимостью идентификаторов, например именами свойств и методов, их можно применять к программному коду независимо от того, внутри или вне пакета он находится. Пакеты позволяют организовать файлы классов, а пространства имен позволяют управлять видимостью отдельных свойств и методов.

Пакеты

Пакеты в ActionScript 3.0 реализуются с помощью пространств имен, но эти два понятия не являются синонимами. При объявлении пакета явно создается специальный тип пространства имен, который гарантированно будет известен во время компиляции. Пространства имен, когда создаются явным образом, не обязательно известны во время компиляции.

В следующем примере для создания простого пакета, содержащего один класс, используется директива package .

package samples 
{ 
    public class SampleCode 
    { 
        public var sampleGreeting:String; 
        public function sampleFunction() 
        { 
            trace(sampleGreeting + " from sampleFunction()"); 
        } 
    } 
}

Именем класса в данном случае является SampleCode. Поскольку класс находится внутри пакета примеров, компилятор автоматически уточняет имя класса во время компиляции, воссоздавая полное имя: samples.SampleCode. Компилятор также уточняет имена любых свойств или методов, так что имена sampleGreeting и sampleFunction() становятся соответственно samples.SampleCode.sampleGreeting и samples.SampleCode.sampleFunction() .

Многие разработчики, особенно те, кто программирует на Java, могут захотеть размещать классы только на верхнем уровне пакетов. В тоже время ActionScript 3.0 поддерживает на верхнем уровне пакета не только классы, но и переменные, функции и даже инструкции. Одним из более сложных способов использования этой функции является определение пространства имен на верхнем уровне пакета, чтобы оно было доступно для всех классов этого пакета. Однако следует обратить внимание, что на верхнем уровне пакета разрешены только два спецификатора доступа — public и internal . В отличии от Java, где можно объявлять вложенные классы частными, в ActionScript 3.0 не поддерживаются ни вложенные, ни частные классы.

Во многом другом, однако, пакеты ActionScript 3.0 сходны с пакетами в языке программирования Java. Как видно из предыдущего примера, полностью уточненные ссылки на пакет передаются с помощью оператора точки ( . ), как это делается в Java. Можно использовать пакеты для организации собственного программного кода в интуитивно понятную иерархическую структуру, с которой смогут работать другие программисты. Это упрощает совместную работу с программными кодами, позволяя создавать собственные пакеты для использования другими разработчиками, а также применять пакеты, созданные другими, в собственном программном коде.

Использование пакетов также помогает соблюсти уникальность имен применяемых идентификаторов и предотвратить конфликты с именами других идентификаторов. Действительно, кто-то наверное скажет, что это основное преимущество использования пакетов. Например, два программиста, которым необходимо совместно использовать программные коды, могут по отдельности создать класс с названием SampleCode. Без пакетов при этом возникнет конфликт имен, единственным решением которого будет переименование одного из классов. Однако благодаря использованию пакетов конфликт имен легко предотвращается, поскольку один, а лучше оба класса можно поместить в пакеты с уникальными именами.

Также в имена пакетов можно включить встроенные точки, чтобы создать вложенные пакеты. Это позволяет создавать иерархическую структуру пакетов. Хорошим примером служит пакет flash.display, предоставляемый в рамках ActionScript 3.0. Пакет flash.display вложен в пакет flash.

Язык ActionScript 3.0 главным образом организован внутри пакета flash. Например, пакет flash.display содержит прикладной программный интерфейс списка отображения, а пакет flash.events содержит новую модель событий.

Создание пакетов

ActionScript 3.0 обеспечивает значительную гибкость в способах организации пакетов, классов и исходных файлов. В предыдущих версиях ActionScript разрешалось размещать только один класс в одном исходном файле, а также требовалось совпадение имен файла и класса. ActionScript 3.0 позволяет включать несколько классов в один исходный файл, но только один класс в каждом файле может быть сделан доступным для программного кода, внешнего по отношению к файлу. Говоря другими словами, только один класс в каждом файле может быть объявлен в декларации пакета. Необходимо объявлять любые дополнительные классы вне определения пакета, что сделает такие классы невидимыми для программного кода вне этого исходного файла. Имя класса, объявляемого в определении пакета, должно соответствовать имени исходного файла.

ActionScript 3.0 обеспечивает более гибкие возможности при объявлении пакетов. В предыдущих версиях ActionScript пакеты всего лишь представляли собой каталоги, в которые помещались исходные файлы, и эти пакеты объявлялись не с помощью инструкции package , а просто имя пакета включалось как часть полного уточненного имени класса в определение класса. Хотя пакеты по-прежнему представляют собой каталоги в ActionScript 3.0, они могут содержать не только классы. В ActionScript 3.0 для объявления пакета используется инструкция package , что означает, что также можно объявлять переменные, функции и пространства имен на верхнем уровне пакета. На верхнем уровне пакета также можно добавлять выполняемые инструкции. Если на верхнем уровне пакета объявляются переменные, функции или пространства имен, то единственными атрибутами, доступными на этом уровне, являются public и internal . Только одно объявление пакета в файле может использовать атрибут public независимо от того, объявляется ли при этом класс, переменная, функция или пространство имен.

Пакеты особенно полезны при упорядочивании программных кодов, а также для предотвращения конфликтов имен. Но не нужно путать концепцию пакетов с несвязанной с ней концепцией наследования классов. Два класса, находящиеся в одном и том же пакете, имеют общее пространство имен, но совсем не обязательно, что они связаны между собой каким-то иным образом. Сходным образом вложенный пакет может не иметь семантических связей со своим родительским пакетом.

Импорт пакетов

Если требуется использовать класс, находящийся внутри пакета, необходимо выполнить импорт либо этого пакета, либо определенного класса. Это отличается от технологии, используемой в ActionScript 2.0, где импорт классов был необязателен.

Например, рассмотрим пример класса SampleCode, представленного ранее. Если класс находится в пакете с именем samples, необходимо использовать одну из следующих инструкций import, прежде чем использовать класс SampleCode.

import samples.*;

или

import samples.SampleCode;

В целом, инструкции import должны быть максимально конкретизированы. Если планируется использовать только класс SampleCode из пакета samples , необходимо импортировать только класс SampleCode, но не весь пакет, к которому он принадлежит. Импорт всего пакета может привести к неожиданным конфликтам имен.

Необходимо также размещать исходный код, определяющий пакет или класс, внутри подкаталога классов . Путь к классам — это задаваемый пользователем список путей к локальным каталогам, определяющий, где компилятор ищет импортированные пакеты и классы. Подкаталог классов иногда называют путем построения или исходным путем .

После того как класс и пакет правильно импортированы можно использовать либо полностью уточненное имя класса (samples.SampleCode), либо просто само имя класса (SampleCode).

Полностью уточненные имена очень полезны в тех случаях, когда одинаковые имена классов, методов или свойств приводят к неоднозначностям в программных кодах, но могут усложнить управление, если использовать такие имена для всех идентификаторов. Например, использование полностью уточненных имен приводит к слишком подробному коду при создании экземпляра класса SampleCode.

var mySample:samples.SampleCode = new samples.SampleCode();

По мере увеличения уровней вложенности пакетов читаемость такого программного кода снижается. Поэтому в тех случаях, когда есть уверенность, что неоднозначность идентификаторов может вызвать проблемы, можно упрощать программный код, используя простые идентификаторы. Например, для создания нового экземпляра класса SampleCode требуется гораздо меньше информации, если использовать только идентификатор класса.

var mySample:SampleCode = new SampleCode();

Если попытаться использовать имена идентификаторов без предварительного импорта соответствующего пакета или класса, компилятор не сможет найти определения классов. С другой стороны, если импортировать пакет или класс, то любая попытка определить имя, конфликтующее с именем импортированного объекта, приведет к возникновению ошибки.

Если создается пакет, спецификатор доступа по умолчанию для всех членов этого пакета установлен как internal , и это означает, что по умолчанию члены пакета видны только другим членам этого пакета. Если требуется, чтобы класс был доступен для программного кода вне пакета, необходимо объявить этот класс как public . Например, в следующем пакете содержатся два класса, SampleCode и CodeFormatter.

// SampleCode.as file 
package samples 
{ 
    public class SampleCode {} 
} 
 
// CodeFormatter.as file 
package samples 
{ 
    class CodeFormatter {} 
}

Класс SampleCode виден вне пакета, потому что он объявлен как класс public . Класс CodeFormatter, в то же время, видим только внутри самого пакета samples. При попытке обращения к классу CodeFormatter за пределами пакета samples появится сообщение об ошибке, как показано в следующем примере.

import samples.SampleCode; 
import samples.CodeFormatter; 
var mySample:SampleCode = new SampleCode(); // okay, public class 
var myFormatter:CodeFormatter = new CodeFormatter(); // error

Если требуется, чтобы оба класса были доступны за пределами пакета, необходимо объявить оба этих класса как public . Нельзя применить атрибут public при объявлении пакета.

Полностью уточненные имена полезны при разрешении конфликтов имен, которые могут произойти при использовании пакетов. Такие сценарии могут возникать, если импортируются два пакета, определяющие классы с одинаковым идентификатором. Например, рассмотрим следующий пакет, в котором также имеется класс с названием SampleCode.

package langref.samples 
{ 
    public class SampleCode {} 
}

Если импортировать оба класса, как показано далее, возникнет конфликт имен при использовании класса SampleCode.

import samples.SampleCode; 
import langref.samples.SampleCode; 
var mySample:SampleCode = new SampleCode(); // name conflict

Компилятор не сможет распознать, какой класс SampleCode использовать. Чтобы разрешить этот конфликт, необходимо использовать полностью уточненное имя.

var sample1:samples.SampleCode = new samples.SampleCode(); 
var sample2:langref.samples.SampleCode = new langref.samples.SampleCode();
Примечание. Программисты со стажем работы на C++ часто путают инструкцию import с #include . Директива #include необходима в C++, поскольку компиляторы этого языка одновременно обрабатывают только один файл и не будут открывать другие файлы в поисках определений классов, пока явно не включен файл заголовка. В ActionScript 3.0 также есть директива include , но она не предназначена для импорта классов и пакетов. Для импорта классов и пакетов в ActionScript 3.0 необходимо использовать инструкцию import и помещать исходный файл, содержащий пакет, в подкаталог классов.

Пространства имен

Пространства имен позволяют управлять доступностью создаваемых свойств и методов. Можно рассматривать спецификаторы управления доступом public , private , protected и internal как встроенные пространства имен. Если эти предварительно определенные спецификаторы управления доступом не соответствуют имеющимся требованиям, можно создать собственные пространства имен.

Тем, кто имеет представление о пространствах имен XML, большая часть обсуждаемой здесь информации покажется знакомой, хотя синтаксис и подробные сведения реализации пространств имен в ActionScript слегка отличаются от используемых в XML. Но даже если раньше работать с пространствами имен не приходилось, эта концепция достаточно понятна, хотя в ее реализации используется специфическая терминология, которую нужно освоить.

Чтобы разобраться, как работают пространства имен, полезно знать, что имя свойства или метода всегда состоит из двух частей: идентификатора и пространства имен. Идентификатор — это та часть, которая обычно воспринимается как имя. Например, идентификаторами в следующем классе являются sampleGreeting и sampleFunction() .

class SampleCode 
{ 
    var sampleGreeting:String; 
    function sampleFunction () { 
        trace(sampleGreeting + " from sampleFunction()"); 
    } 
}

В тех случаях, когда определения не предваряются атрибутом пространства имен, их имена по умолчанию уточняются как относящиеся к пространству имен internal , и это означает, что они видимы для вызова только в рамках данного пакета. Если для компилятора задан строгий режим работы, он создает предупреждение о применении пространства имен internal к любому идентификатору без атрибута пространства имен. Для обеспечения широкого доступа к идентификатору необходимо специально предварять его имя атрибутом public . В предыдущем примере программного кода для обоих классов sampleGreeting и sampleFunction() в качестве значения пространства имен указано internal .

При использовании пространств имен необходимо выполнить три основных шага. Прежде всего необходимо определить пространство имен, используя ключевое слово namespace . Например, в следующем программном коде определяется пространство имен version1 .

namespace version1;

Во-вторых, необходимо применить пространство имен, используя его вместо спецификатора контроля доступа при объявлении свойства или метода. В следующем примере функция с именем myFunction() размещается в пространстве имен version1 .

version1 function myFunction() {}

В-третьих, как только пространство имен будет применено, к нему можно адресоваться с помощью директивы use или уточняя имя идентификатора указанием пространства имен. В следующем примере функция myFunction() вызывается с использованием директивы use .

use namespace version1; 
myFunction();

Для вызова функции myFunction() можно также использовать уточненное имя, как показано в следующем примере.

version1::myFunction();

Определение пространств имен

Пространства имен содержат одно значение, универсальный идентификатор ресурса (URI), который иногда называется именем пространства имен . Идентификатор URI позволяет обеспечить уникальность определения пространства имен.

Пространство имен создается путем объявления определения пространства имен одним из следующих способов. Можно либо определить пространство имен путем явного указания URI, как это делалось бы для пространства имен XML, либо можно пропустить URI. В следующем примере показано, как можно определить пространство имен с помощью URI.

namespace flash_proxy = "http://www.adobe.com/flash/proxy";

Идентификатор URI выступает в качестве уникальной строки идентификации пространства имен. Если идентификатор URI пропущен, как показано в следующем примере, компилятор создаст уникальную внутреннюю строку идентификации вместо этого URI. Доступ пользователя к этой внутренней строке идентификации невозможен.

namespace flash_proxy;

Как только пространство имен определено, с использованием URI или без его использования, это пространство имен не может быть переопределено в той же области действия. Попытка определить пространство имен, которое уже было определено ранее в этой области действия, приведет к ошибке компилятора.

Если пространство имен определяется в пакете или классе, это пространство имен может быть невидимо для программного кода за пределами этого пакета или класса до тех пор, пока не будет использован соответствующий спецификатор контроля доступа. Например, в следующем программном коде показано пространство имен flash_proxy , определенное в пакете flash.utils. В следующем примере отсутствие спецификатора контроля доступа означает, что пространство имен flash_proxy будет видимо только программному коду в самом пакете flash.utils и будет недоступно любому программному коду вне пакета.

package flash.utils 
{ 
    namespace flash_proxy; 
}

В следующем программном коде атрибут public используется для открытия доступа к пространству имени flash_proxy извне этого пакета.

package flash.utils 
{ 
    public namespace flash_proxy; 
}

Применение пространств имен

Применение пространства имен означает размещение определения в пространстве имен. В число определений, которые можно помещать в пространства имен, входят функции, переменные и постоянные (нельзя поместить класс в произвольное пространство имен).

Например, рассмотрим функцию, объявленную с использованием пространства имен для контроля доступа public . Использование атрибута public в определении функции вызывает ее размещение в пространстве имен public, что делает эту функцию доступной для всех программных кодов. Как только пространство имен задано, можно использовать его тем же способом, что и атрибут public , и определение доступно программному коду, из которого можно обращаться к произвольно заданным пространствам имен. Например, если определяется пространство имен example1 , можно добавить метод с названием myFunction() , используя в качестве атрибута example1 , как показано в следующем примере.

namespace example1; 
class someClass 
{ 
    example1 myFunction() {} 
}

Объявление метода myFunction() с использованием пространства имен example1 в качестве атрибута означает, что этот метод принадлежит пространству имен example1 .

При применении пространств имен необходимо учитывать следующие правила.

  • Для каждого объявления можно применять только одно пространство имен.

  • Невозможно применить атрибут пространства имен более чем к одному определению одновременно. Говоря другими словами, если потребуется применить пространство имен к десяти различным функциям, необходимо будет добавить это пространство имен в качестве атрибута к определению каждой из десяти функций.

  • Если применяется пространство имен, нельзя также указывать спецификатор контроля доступа, поскольку пространства имен и спецификаторы контроля доступа взаимоисключают друг друга. То есть нельзя объявить функцию или свойство с атрибутом public , private , protected или internal в дополнение к применению пространства имен.

Привязка пространств имен

Нет необходимости явно выполнять привязку к пространствам имен, если используется метод или свойство, объявленные с каким-либо пространством имен для контроля доступа, например атрибутами public , private , protected и internal . Доступ к этим специальным пространствам имен управляется контекстом. Например, определения, помещенные в пространство имен private , автоматически доступны для программных кодов в рамках данного класса. Однако для самостоятельно определенных пространств имен такой контекстной чувствительности не существует. Чтобы использовать метод или свойство, помещенное в произвольное пространство имен, необходимо создать ссылку на это пространство имен.

Привязку пространства имен можно выполнить с помощью директивы use namespace или можно уточнить имя с помощью пространства имен, используя в качестве уточняющего имя знака пунктуации ( :: ). Привязка пространства имен с помощью директивы use namespace открывает пространство имен, так что его можно применять к любому неуточненному идентификатору. Например, если определено пространство имен example1 , можно обращаться к именам в этом пространстве, используя директиву use namespace example1 .

use namespace example1; 
myFunction();

Одновременно может быть открыто сразу несколько пространств имен. Как только пространство имен открыто с помощью директивы use namespace , оно становится открытым для всего блока программного кода, в котором оно открыто. Не существует способов явным образом закрыть пространство имен.

Открытие нескольких пространств имен увеличивает вероятность возникновения конфликтов имен. Если предпочтительнее не открывать пространство имен, можно избежать директивы use namespace , просто уточняя имя метода или свойства с помощью пространства имен и уточняющего имя знака пунктуации. Например, в следующем программном коде показано, как можно уточнить имя myFunction() , используя пространство имен example1 .

example1::myFunction();

Использование пространств имен

Можно найти конкретный пример пространства имен, используемого для предотвращения конфликтов имен в классе flash.utils.Proxy, который входит в состав ActionScript 3.0. Класс Proxy, заменивший класс Object.__resolve , который использовался в ActionScript 2.0, позволяет перехватывать обращения к неопределенным свойствам или методам перед тем, как произойдет ошибка. Все методы класса Proxy находятся в пространстве имен flash_proxy , чтобы предотвратить конфликты имен.

Чтобы лучше понять, как используется пространство имен flash_proxy , необходимо разобраться с порядком использования класса Proxy. Функциональные возможности класса Proxy доступны только для классов, наследуемых из него. То есть если потребуется использовать методы класса Proxy к объекту, определение класса объекта должно расширять класс Proxy. Например, если требуется перехватить попытки вызова неопределенных методов, следует расширить класс Proxy, а затем переписать метод callProperty() в классе Proxy.

Необходимо помнить, что применение пространств имен обычно включает три этапа: определение, применение и привязку. Поскольку методы класса Proxy никогда не вызываются явным образом, то пространство имен flash_proxy только определяется и применяется, но никогда не привязывается. ActionScript 3.0 определяет пространство имен flash_proxy и применяет его в классе Proxy. Затем программному коду необходимо только применить пространство имен flash_proxy к классам, расширяющим класс Proxy.

Пространство имен flash_proxy определяется в пакете flash.utils способом, аналогичным следующему.

package flash.utils 
{ 
    public namespace flash_proxy; 
}

Пространство имен применяется к методам класса Proxy, как показано в следующей выборке из класса Proxy.

public class Proxy 
{ 
    flash_proxy function callProperty(name:*, ... rest):* 
    flash_proxy function deleteProperty(name:*):Boolean 
    ... 
}

Как показано в следующем программном коде, необходимо вначале импортировать как класс Proxy, так и пространство имен flash_proxy . Затем следует объявить свой класс, расширяющий класс Proxy (необходимо также добавить атрибут dynamic , если предполагается компиляция в строгом режиме). Когда метод callProperty() будет перезаписан, необходимо использовать пространство имен flash_proxy .

package 
{ 
    import flash.utils.Proxy; 
    import flash.utils.flash_proxy; 
 
    dynamic class MyProxy extends Proxy 
    { 
        flash_proxy override function callProperty(name:*, ...rest):* 
        { 
            trace("method call intercepted: " + name); 
        } 
    } 
}

Если создается экземпляр класса MyProxy и вызывается неопределенный метод, например метод testing() , см. следующий пример, то объект Proxy перехватывает вызов метода и выполняет инструкции, находящиеся в перезаписанном методе callProperty() (в данном случае простую инструкцию trace() ).

var mySample:MyProxy = new MyProxy(); 
mySample.testing(); // method call intercepted: testing

Расположение методов класса Proxy в пространстве имен flash_proxy обеспечивает два преимущества. Во-первых, отдельное пространство имен уменьшает беспорядок интерфейса общего доступа для любого класса, расширяющего класс Proxy. (В классе Proxy имеется около двенадцати методов, которые можно перезаписать, все они не предназначены для прямого вызова. Размещение всех их в пространстве имен public может привести к сбоям.) Во-вторых, использование пространства имен flash_proxy предотвращает конфликты имен, в случае если подкласс Proxy содержит методы экземпляров с именами, совпадающими с какими-либо именами методов в классе Proxy. Например, может потребоваться назвать один из создаваемых методов callProperty() . Показанный далее программный код допустим, поскольку созданная версия метода callProperty() находится в другом пространстве имен.

dynamic class MyProxy extends Proxy 
{ 
    public function callProperty() {} 
    flash_proxy override function callProperty(name:*, ...rest):* 
    { 
        trace("method call intercepted: " + name); 
    } 
}

Пространства имен также могут быть полезны, если требуется обеспечить доступ к методам или свойствам таким способом, который нельзя организовать четырьмя спецификаторами контроля доступа ( public , private , internal и protected ). Например, может получиться, что несколько служебных методов распределены между несколькими пакетами. Требуется, чтобы эти методы были доступны для всех этих пакетов, но в то же время методы должны быть закрыты для общего доступа. Для решения этой задачи можно создать несколько пространств имен и использовать их в качестве собственных специальных спецификаторов контроля доступа.

В следующем примере определенное пользователем пространство имен используется для группировки двух функций, находящихся в разных пакетах. Группируя их в одном пространстве имен, можно сделать обе функции видимыми для класса и пакета, используя одну инструкцию use namespace .

В этом примере, чтобы продемонстрировать данную технологию, используется четыре файла. Все эти файлы должны находиться в подкаталоге классов. Первый файл, myInternal.as, используется для определения пространства имен myInternal . Поскольку этот файл находится в пакете с именем example, необходимо поместить файл в папку с именем example. Пространство имен помечено как public , поэтому его можно импортировать в другие пакеты.

// myInternal.as in folder example 
package example 
{ 
    public namespace myInternal = "http://www.adobe.com/2006/actionscript/examples"; 
}

Второй и третий файлы, Utility.as и Helper.as, определяют классы, которые содержат методы, доступные для других пакетов. Класс Utility находится в пакете example.alpha, что означает, что файл должен быть помещен в папку alpha, вложенную в папку example. Класс Helper находится в пакете example.beta, что означает, что файл должен быть помещен в папку beta, вложенную в папку example. Оба этих пакета, example.alpha и example.beta, должны импортировать пространство имен, прежде чем использовать его.

// Utility.as in the example/alpha folder 
package example.alpha 
{ 
    import example.myInternal; 
     
    public class Utility 
    { 
        private static var _taskCounter:int = 0; 
     
        public static function someTask() 
        { 
            _taskCounter++; 
        } 
         
        myInternal static function get taskCounter():int 
        { 
            return _taskCounter; 
        } 
    } 
} 
 
// Helper.as in the example/beta folder 
package example.beta 
{ 
    import example.myInternal; 
     
    public class Helper 
    { 
        private static var _timeStamp:Date; 
         
        public static function someTask() 
        { 
            _timeStamp = new Date(); 
        } 
         
        myInternal static function get lastCalled():Date 
        { 
            return _timeStamp; 
        } 
    } 
}

Четвертый файл, NamespaceUseCase.as, является основным классом приложения и должен располагаться на одном уровне с папкой example. Во Flash Professional этот класс будет использоваться в качестве класса документа для FLA. Класс NamespaceUseCase также импортирует пространство имен myInternal и использует его для вызова двух статических методов, которые находятся в других пакетах. Статические методы в этом примере использованы исключительно для упрощения программного кода. Оба метода, статический и создающий экземпляр, могут размещаться в пространстве имен myInternal .

// NamespaceUseCase.as 
package 
{ 
    import flash.display.MovieClip; 
    import example.myInternal; // import namespace 
    import example.alpha.Utility;// import Utility class 
    import example.beta.Helper;// import Helper class 
     
    public class NamespaceUseCase extends MovieClip 
    { 
        public function NamespaceUseCase() 
        { 
            use namespace myInternal; 
             
            Utility.someTask(); 
            Utility.someTask(); 
            trace(Utility.taskCounter); // 2 
             
            Helper.someTask(); 
            trace(Helper.lastCalled); // [time someTask() was last called] 
        } 
    } 
}